package com.iflytek.jzcpx.procuracy.ocr.service.impl;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.iflytek.icourt.file.export.impl.TxtFileExportStrategy;
import com.iflytek.icourt.model.pdf.Cell;
import com.iflytek.icourt.model.pdf.Char;
import com.iflytek.icourt.model.pdf.Page;
import com.iflytek.icourt.model.pdf.Rect;
import com.iflytek.icourt.model.pdf.Sent;
import com.iflytek.icourt.model.pdf.Table;
import com.iflytek.icourt.model.pdf.TextRegion;
import com.iflytek.icourt.model.pdf.Textline;
import com.iflytek.jzcpx.procuracy.common.enums.CommonResultEnum;
import com.iflytek.jzcpx.procuracy.common.exception.InvalidParameterException;
import com.iflytek.jzcpx.procuracy.common.exception.ViewException;
import com.iflytek.jzcpx.procuracy.common.result.Result;
import com.iflytek.jzcpx.procuracy.common.result.WebResult;
import com.iflytek.jzcpx.procuracy.common.util.IdWokerUtil;
import com.iflytek.jzcpx.procuracy.common.util.ImageUtil;
import com.iflytek.jzcpx.procuracy.common.util.JSONUtil;
import com.iflytek.jzcpx.procuracy.common.util.StopWatch;
import com.iflytek.jzcpx.procuracy.common.util.ThreadPoolUtils;
import com.iflytek.jzcpx.procuracy.ocr.common.constant.Constants;
import com.iflytek.jzcpx.procuracy.ocr.common.constant.OcrConstants;
import com.iflytek.jzcpx.procuracy.ocr.common.enums.OcrFileStatusEnum;
import com.iflytek.jzcpx.procuracy.ocr.common.enums.OcrResultEnum;
import com.iflytek.jzcpx.procuracy.ocr.common.enums.OcrTaskStatusEnum;
import com.iflytek.jzcpx.procuracy.ocr.common.enums.RecognizeFileStatusEnum;
import com.iflytek.jzcpx.procuracy.ocr.common.enums.RecognizeFuncEnum;
import com.iflytek.jzcpx.procuracy.ocr.common.enums.RecognizeTaskStatusEnum;
import com.iflytek.jzcpx.procuracy.ocr.common.enums.TaskFileTypeEnum;
import com.iflytek.jzcpx.procuracy.ocr.common.helper.DzjzDataDTO;
import com.iflytek.jzcpx.procuracy.ocr.common.helper.DzjzWjDTO;
import com.iflytek.jzcpx.procuracy.ocr.common.helper.PdfComposeUtil;
import com.iflytek.jzcpx.procuracy.ocr.common.helper.SwxRequestHelper;
import com.iflytek.jzcpx.procuracy.ocr.common.util.pti.impl.GsUtil;
import com.iflytek.jzcpx.procuracy.ocr.component.mq.MessageProducer;
import com.iflytek.jzcpx.procuracy.ocr.entity.Metric;
import com.iflytek.jzcpx.procuracy.ocr.entity.OcrFile;
import com.iflytek.jzcpx.procuracy.ocr.entity.OcrTask;
import com.iflytek.jzcpx.procuracy.ocr.entity.RecognizeFile;
import com.iflytek.jzcpx.procuracy.ocr.entity.RecognizeTask;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.BatchOcrVO;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.JzInfoResult;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.JzmlInfo;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.JzmlInfoResult;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.Jzmlwj;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.OcrWJsbMultiResult;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.OcrWJsbSingleResult;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgRes;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjjgxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjjgxxWbxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjjgxxWbxxHwbzb;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxxBgqyTable;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxxBgqyTableTrs;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxxBgqyTableTrsTds;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxxBgqyText;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxxSxqy;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxxWzqy;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.WjOcrsbjgResDataWjsbxxWzqyH;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.Wjsbxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.PageOcrInfo;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Sxxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wbxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wbzb;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjjgxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbjg;
import com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Zfxx;
import com.iflytek.jzcpx.procuracy.ocr.entity.txsb.JzRecognizeResult;
import com.iflytek.jzcpx.procuracy.ocr.recognize.RecognizeFuncHandler;
import com.iflytek.jzcpx.procuracy.ocr.recognize.model.OcrResultQueryDto;
import com.iflytek.jzcpx.procuracy.ocr.recognize.model.OcrResultRequestVo;
import com.iflytek.jzcpx.procuracy.ocr.recognize.model.Wjtz;
import com.iflytek.jzcpx.procuracy.ocr.service.MetricService;
import com.iflytek.jzcpx.procuracy.ocr.service.OcrFileService;
import com.iflytek.jzcpx.procuracy.ocr.service.OcrService;
import com.iflytek.jzcpx.procuracy.ocr.service.OcrTaskService;
import com.iflytek.jzcpx.procuracy.ocr.service.RecognizeFileService;
import com.iflytek.jzcpx.procuracy.ocr.service.RecognizeTaskService;
import com.iflytek.jzcpx.procuracy.tools.common.PropUtils;
import com.iflytek.jzcpx.procuracy.tools.ocr.OcrClient;
import com.iflytek.jzcpx.procuracy.tools.ocr.OcrUtil;
import com.iflytek.jzcpx.procuracy.tools.ocr.pdf.OcrResult;
import com.iflytek.sxs.dfs.client.FdfsClient;
import com.lowagie.text.Image;
import com.yomahub.tlog.core.thread.TLogInheritableTask;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import static com.iflytek.jzcpx.procuracy.tools.ocr.OcrConstants.PDFCompose.OCR_HANDWRITE_TYPE;

/**
 * @author <a href=mailto:ktyi@iflytek.com>伊开堂</a>
 * @date 2019-08-09 16:55
 */
@Service
public class OcrServiceImpl implements OcrService {
    private static final Logger logger = LoggerFactory.getLogger(OcrServiceImpl.class);

    private final ThreadPoolExecutor PDF_CREATE_THREAD_POOL = new ThreadPoolExecutor(8, 8, 0, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            new ThreadFactoryBuilder().setDaemon(false).setNameFormat("PDF-Compose-Thread-%d")
                    .setUncaughtExceptionHandler(((t, e) -> logger.error("线程 " + t + " 异常结束", e))).build(),
            new ThreadPoolExecutor.CallerRunsPolicy());
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    private OcrTaskService ocrTaskService;
    @Autowired
    private OcrFileService ocrFileService;
    @Autowired
    private RecognizeTaskService recognizeTaskService;
    @Autowired
    private RecognizeFileService recognizeFileService;

    @Autowired
    private OcrClient ocrClient;
    @Autowired
    private RecognizeFuncHandler funcHandler;
    @Autowired
    private SwxRequestHelper requestHelper;
    @Autowired
    private FdfsClient fdfsClient;
    @Autowired
    private MessageProducer messageProducer;

    @Autowired
    @Qualifier("ocrComposeExecutor")
    private ThreadPoolExecutor ocrCompoeExecutor;

    @Value("${skynet.ocr.compressThreshold:2500}")
    private int compressThreshold;

    @Value("${push.fail.nums:3}")
    private int maxFailNums;

    @Autowired
    private TxtFileExportStrategy txtFileExportStrategy;

    @Autowired
    private GsUtil gsUtil;

    @Autowired
    private MetricService metricService;

    @Resource(name = "ocrExecutor")
    private ThreadPoolExecutor ocrExecutor;
    @Resource(name = "recognizeExecutor")
    private ThreadPoolExecutor recognizeExecutor;

    /**
     *  逻辑调整，2022-01-06 bbqian3
     *  1. 有文件序号，直接掉接口下载文件，ocr识别，返回结果
     *  2. 只有部门授案号，查数据库，根据文件序号下文件，上传结果并返回
     */
    public OcrWJsbMultiResult findOcrResult(String bmsah, OcrResultQueryDto param) {
        OcrWJsbMultiResult ocrWJsbMultiResult = new OcrWJsbMultiResult();

        ocrWJsbMultiResult.setBmsah(bmsah);
        ocrWJsbMultiResult.setWjxhlist(new ArrayList<>());
        List<String> wjxhList = new ArrayList<String>();
        List<OcrFile> ocrFileList = null;

        Wjsbxx wjsbxx = null;
        List<Wjsbxx> wjxhlist = new ArrayList<>();
        if (ArrayUtils.isNotEmpty(param.getWjxhlist())) {
            //1. 有文件序号，直接掉接口，查询文件
            wjxhList = Arrays.asList(param.getWjxhlist());
            for (String wjxh : wjxhList) {
                String ocrJSON = ocrAnew(wjxh);
                // 构造文件识别信息
                if (!StringUtils.isEmpty(ocrJSON)) {
                    wjsbxx = new Wjsbxx();
                    Wjjgxx wjjgxx = buildWjjgxx(ocrJSON, wjsbxx);
                    wjsbxx.setWjxh(wjxh);
                    wjsbxx.setWjjgxx(wjjgxx);
                    wjxhlist.add(wjsbxx);
                }
            }
        } else {
            //2. 只有部门授案号，查数据库，返回结果
            ocrFileList = ocrFileService.findBybmsah(bmsah);
            for (OcrFile ocrFile : ocrFileList) {
                String wjxh = ocrFile.getWjxh();
                String ocrJSON = ocrAnew(wjxh);
                // 构造文件识别信息
                if (!StringUtils.isEmpty(ocrJSON)) {
                    wjsbxx = new Wjsbxx();
                    Wjjgxx wjjgxx = buildWjjgxx(ocrJSON, wjsbxx);
                    wjsbxx.setWjxh(wjxh);
                    wjsbxx.setWjjgxx(wjjgxx);
                    wjxhlist.add(wjsbxx);
                    uploadToFdfs(ocrFile.getId(), ocrJSON);
                }
            }
        }
        ocrWJsbMultiResult.getWjxhlist().addAll(wjxhlist);
        return ocrWJsbMultiResult;
    }

    private void uploadToFdfs(Long fileId, String ocrJSON) {
        // 上传识别结果到 fastDFS
        byte[] bytes = ocrJSON.getBytes(StandardCharsets.UTF_8);
        try (InputStream is = new ByteArrayInputStream(bytes)) {
            String storePath = fdfsClient.uploadFile(is, bytes.length, OcrConstants.OCR_ENGINE_FDFS_SUFFIX);
            ocrFileService.updateEngineResultPath(fileId, storePath);
            logger.info("上传 ocr 识别结果到 fastDFS 成功. fileId: {}", fileId);
        } catch (Exception e) {
            logger.warn("上传 ocr 识别结果到 fastDFS 异常. fileId: {}", fileId, e);
        }
    }

    /**
     * 根据文件序号下载jpg文件，识别返回ocrJSON
     * @param wjxh
     * @return
     */
    private String ocrAnew(String wjxh) {
        logger.info("#########开始下载电子卷宗文件文件序号：{}########:{}", wjxh);
        // 下载文件
        Result<DzjzWjDTO> fileContentResult = requestHelper.ocrFileJpgWjApi(wjxh);
        if (!fileContentResult.isSuccess()) {
            logger.error("#########下载电子卷宗文件失败，序号：{}########:{}", wjxh);
            // 文件下载失败, 该文件识别任务结束
            return StringUtils.EMPTY;
        }

        // 二进制图片文件内容
        byte[] fileBytes = fileContentResult.getData().getData();

        // ocr 识别
        Result<String> ocrResult = null;
        String ocrJson = null;
        Path file = null;
        try {
            file = Files.createTempFile(UUID.randomUUID().toString(), ".jpg");
            FileUtils.writeByteArrayToFile(file.toFile(), fileBytes);
            // 应用退出时删除文件, 兜底
            file.toFile().deleteOnExit();

            // 调用 ocr 引擎
            ocrResult = ocrClient.requestRestTemplate(file.toFile());
            if (!ocrResult.isSuccess()) {
                return StringUtils.EMPTY;
            }
            ocrJson = ocrResult.getData();
            logger.info("ocr识别结果大小：" + ocrJson.length());
        } catch (Exception e) {
            logger.error("ocr识别异常. wjxh: {}", wjxh, e);
            return StringUtils.EMPTY;
        } finally {
            if (file.toFile() != null) {
                // 删除临时文件
                FileUtils.deleteQuietly(file.toFile());
            }
        }
        return ocrResult.getData();
    }

    @Override
    public Result<OcrWJsbSingleResult> ocrToPdfDetail(byte[] fileBytes, String wjbh, String wjhz) {
        try {
            logger.info("单文件识别. number: {}, suffix: {}", wjbh, wjhz);
            // 保存识别记录
            Long fileId = ocrFileService.createSingle(wjbh);
            // ocr 识别
            Result<String> ocrResult = doOcr(fileId, fileBytes, wjbh, null);
            if (!ocrResult.isSuccess()) {
                return Result.from(ocrResult);
            }

            // 合成pdf
            String ocrJson = ocrResult.getData();
            Result<byte[]> composeResult = composePdf(fileId, fileBytes, ocrJson);
            if (!composeResult.isSuccess()) {
                return Result.from(composeResult);
            }

            byte[] pdfFileBytes = composeResult.getData();

            // file = Files.createTempFile(fileName, ".jpg");
            // Path file;

            Wjsbxx wjsbxx = new Wjsbxx();
            try {
                // file = Files.createTempFile(UUID.randomUUID().toString(), ".pdf");
                // FileUtils.writeByteArrayToFile(file.toFile(), composeResult.getData());
                // 读取图片
                Image image = Image.getInstance(fileBytes);

                // 设置宽度和高度
                wjsbxx.setWjgd((int) image.getPlainHeight());
                wjsbxx.setWjkd((int) image.getPlainWidth());
                OcrResult ocrResultObj = PdfComposeUtil.getOcrResult(ocrJson);
                String jd = PdfComposeUtil.getCurrentionRoration(ocrResultObj).toString();
                wjsbxx.setWjqxjd(jd);
            } catch (Exception ex) {
                // ex.printStackTrace();
            }
            OcrWJsbSingleResult data = new OcrWJsbSingleResult();
            data.setWjbh(wjbh);
            data.setWjhz(Constants.SupportExt.PDF);
            data.setWjsbxx(new ArrayList<Wjsbxx>());
            data.setFileId(fileId);
            wjsbxx.setWjnr(new String(Base64.encodeBase64(pdfFileBytes), Charset.forName("utf-8")));
            wjsbxx.setWjwb(OcrUtil.getTextByType(ocrJson, Constants.STR_TWO).replace("\n", " "));
            data.getWjsbxx().add(wjsbxx);
            return Result.success(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.failed(CommonResultEnum.FAILED);
    }

    /**
     * 根据识别结果，合成双层pdf文件
     *
     * @param fileId
     * @param fileBytes
     * @param ocrJson
     *
     * @return
     */
    private Result<byte[]> composePdf(Long fileId, byte[] fileBytes, String ocrJson) {
        long start = System.currentTimeMillis();
        logger.info("合成双层pdf文件, fileId: {}, fileByteSize: {}, ocrJsonSize: {}", fileId,
                    fileBytes == null ? 0 : fileBytes.length, StringUtils.length(ocrJson));
        byte[] pdfFileBytes = new byte[0];
        try {
            pdfFileBytes = new PdfComposeUtil().handleCompose(fileBytes, ocrJson);
            ocrFileService.updateStatus(fileId, OcrFileStatusEnum.COMPOSITE_PDF);
        }
        catch (Exception e) {
            logger.warn("合成双层 pdf 文件异常, fileId: {}.", fileId, e);
            ocrFileService.abnormalEnd(fileId, "合成双层 pdf 文件异常");
            return Result.failed(OcrResultEnum.COMPOSE_PDF_EXCEPTION);
        }
        finally {
            logger.info("合成双层 pdf 文件结束, fileId: {}, pdfSize: {}, 耗时: {}ms", fileId,
                        pdfFileBytes == null ? 0 : pdfFileBytes.length, System.currentTimeMillis() - start);
        }
        return Result.success(pdfFileBytes);
    }

    /**
     * 图片识别
     *
     * @param fileId
     * @param fileBytes
     * @param wjxh
     * @return
     */
    private Result<String> doOcr(Long fileId, byte[] fileBytes, String wjxh, String bsbh) {
        long start = System.currentTimeMillis();
        logger.info("开始单张图片OCR处理, bsbh: {}, wjxh: {}, fileId: {}", bsbh, wjxh, fileId);

        // ocr 识别
        String ocrJson = null;
        Result<String> ocrResult = null;
        if (PropUtils.getBool("reuse_ocr_for_recog", false) && StringUtils.isNotEmpty(wjxh) && StringUtils.isNotEmpty(
                bsbh)) {
            OcrFile existFile = this.ocrFileService.findOcrFileByWjxh(wjxh);
            if (null != existFile) {
                String engineResultPath = existFile.getEngineResultPath();
                String state = existFile.getState();
                if (!Constants.STATE_DELETED.equals(state) && StringUtils.isNotBlank(engineResultPath)) {
                    logger.debug("wjxh:{}文件识别结果在数据库中已存在fileId:{}，无需重复识别,直接更新识别结果路径, bsbh:{}, engineResultPath: {}",
                                 wjxh, fileId, bsbh, engineResultPath);
                    try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                        fdfsClient.downloadFile(outputStream, engineResultPath);
                        ocrJson = outputStream.toString("UTF-8");
                        ocrFileService.updateEngineResultPath(fileId, engineResultPath);
                        return Result.success(ocrJson);
                    }
                    catch (Exception e) {
                        logger.warn("从fastDFS下载ocr引擎原始识别结果异常, 继续调用OCR引擎, filePath: {}", engineResultPath, e);
                    }
                }
            }
        }
        Path file = null;
        try {
            // file = Files.createTempFile(fileName, ".jpg");
            file = Files.createTempFile(UUID.randomUUID().toString(), ".jpg");
            FileUtils.writeByteArrayToFile(file.toFile(), fileBytes);
            // 应用退出时删除文件, 兜底
            file.toFile().deleteOnExit();

            // 调用 ocr 引擎
            ocrResult = ocrClient.requestRestTemplate(file.toFile());
            if (!ocrResult.isSuccess()) {
                ocrFileService.abnormalEnd(fileId, "调用 ocr 引擎失败");
                return Result.failed(OcrResultEnum.OCR_RECOGNIZE_FAILED);
            }
            ocrFileService.updateStatus(fileId, OcrFileStatusEnum.REQUEST_OCR_ENGINE);
            ocrJson = ocrResult.getData();
        } catch (Exception e) {
            logger.warn("ocr识别异常. fileId: {}", fileId, e);
            ocrFileService.abnormalEnd(fileId, "调用 ocr 引擎异常");
            return new Result<>(OcrResultEnum.OCR_RECOGNIZE_EXCEPTION);
        } finally {
            if (file.toFile() != null) {
                // 删除临时文件
                FileUtils.deleteQuietly(file.toFile());
            }
        }

        if (StringUtils.isBlank(ocrJson)) {
            logger.warn("ocr识别为空. fileId: {}", fileId);
            ocrFileService.abnormalEnd(fileId, "ocr引擎识别结果为空");
            return new Result<>(OcrResultEnum.OCR_RECOGNIZE_EXCEPTION);
        }

        // 上传识别结果到 fastDFS
        logger.info("上传 ocr 识别结果到 fastDFS, fileId: {}, jsonLength: {}", fileId, StringUtils.length(ocrJson));
        byte[] bytes = ocrJson.getBytes(StandardCharsets.UTF_8);
        try (InputStream is = new ByteArrayInputStream(bytes)) {
            String storePath = fdfsClient.uploadFile(is, bytes.length, OcrConstants.OCR_ENGINE_FDFS_SUFFIX);
            ocrFileService.updateEngineResultPath(fileId, storePath);
        } catch (Exception e) {
            logger.warn("上传 ocr 识别结果到 fastDFS 异常. fileId: {}", fileId, e);
            ocrFileService.abnormalEnd(fileId, "上传 ocr 识别结果到 fastDFS 异常");
            return Result.failed(OcrResultEnum.UPDATE_ENGINE_RESULT_FAILED);
        }

        logger.info("单张图片OCR处理结束, bsbh: {}, wjxh: {}, fileId: {}, 耗时: {}ms", bsbh, wjxh, fileId,
                    System.currentTimeMillis() - start);
        return ocrResult;
    }

    @Override
    public Result submitBatchOcrToPdf(BatchOcrVO batchOcrVO, JSONObject metricJson) {
        logger.info("提交批量文件转双层pdf任务, batchOcrVO: {}, ocrExecutor: {}", batchOcrVO, ThreadPoolUtils.poolInfo(ocrExecutor));
        StopWatch stopWatch = new StopWatch("submitBatchOcrToPdf");
        stopWatch.start("等待线程池执行");
        ocrExecutor.submit(new TLogInheritableTask() {
            @Override
            public void runTask() {
                //加锁
                String bsbh = batchOcrVO.getBsbh();
                RLock lock = redissonClient.getLock("OCR_Submit_Lock_" + bsbh);
                try {
                    int lockMill = PropUtils.getInt("submit_lock_time", 10) * 1000;
                    if (!lock.tryLock((long) Math.ceil(lockMill / 2f), lockMill, TimeUnit.MILLISECONDS)) {
                        if (PropUtils.getBool("block_ocr_duplicate_submit", false)) {
                            logger.warn("未能获取锁, 当前OCR识别任务在进行，bsbh: {}, stopWatch: {}", bsbh, stopWatch);
                            return;
                        }
                        else {
                            logger.info("当前OCR识别任务正在执行中，bsbh: {}", bsbh);
                        }
                    }

                    logger.info("开始执行批量OCR识别异步任务. taskVO: {}", batchOcrVO);
                    stopWatch.start("历史数据查询");

                    List<OcrTask> ocrTaskList = ocrTaskService.listByBsbh(bsbh);
                    // OCR识别数据库重复记录存储优化
                    logger.info("已存在识别任务数据: ocrTaskList: {}", JSONUtil.toStrDefault(ocrTaskList));
                    Long taskId = 0L;
                    if (CollectionUtils.isNotEmpty(ocrTaskList)) {
                        OcrTask historyOcrTask = CollUtil.getLast(ocrTaskList);
                        taskId = historyOcrTask.getId();
                        logger.info("OCR识别任务标识编号bsbh: {}, 存在历史记录id: {}，将任务状态更新成INIT状态", bsbh, taskId);
                        boolean update = ocrTaskService.updateStatus(taskId, OcrTaskStatusEnum.INIT);
                    }
                    else {
                        // 保存 task 信息
                        taskId = ocrTaskService.create(batchOcrVO.getSystemid(), batchOcrVO.getDwbm(), batchOcrVO.getBsbh(),
                                                       batchOcrVO.getTaskid(), batchOcrVO.getBmsah());
                        logger.info("OCR识别任务标识编号bsbh: {} 不存在历史记录，新增任务, OcrTaskId: {}", bsbh, taskId);
                    }

                    stopWatch.start("swx:获取待识别文件信息");
                    // 获取电子卷宗待识别文件信息
                    Result<JzInfoResult> fileInfoResp = requestHelper.getOCRFileInfo(batchOcrVO.getBsbh(), batchOcrVO.getTaskid());
                    // 更新任务状态
                    if (fileInfoResp == null || !fileInfoResp.isSuccess()) {
                        logger.info("获取电子卷宗待识别文件信息失败, 任务结束, batchOcrVO: {}, stopWatch: {}", batchOcrVO, stopWatch);
                        ocrTaskService.abnormalEnd(taskId, "获取电子卷宗待识别文件信息失败");
                        return;
                    }

                    logger.info("#########获取电子卷宗待识别文件信息（03接口）########:{}", JSON.toJSONString(fileInfoResp));
                    // 卷宗目录文件列表
                    List<Jzmlwj> jzmlwjlist = Optional.ofNullable(fileInfoResp.getData()).map(
                                                              JzInfoResult::getJzmlwjlist).orElse(new ArrayList<>()).stream().filter(Objects::nonNull)
                                                      .filter(wj -> StringUtils.isNotBlank(wj.getWjxh())).collect(Collectors.toList());
                    if (CollectionUtils.isEmpty(jzmlwjlist)) {
                        logger.info("获取电子卷宗待识别文件接口返回的卷宗目录文件列表为空, 任务结束, batchOcrVO: {}, stopWatch: {}",
                                    batchOcrVO, stopWatch);
                        ocrTaskService.abnormalEnd(taskId, "卷宗目录文件列表为空");
                        return;
                    }
                    logger.info("OCR识别-获取电子卷宗待识别文件信息{}条", CollectionUtils.size(jzmlwjlist));

                    //筛选库中不存在记录
                    stopWatch.start("筛选数据");
                    List<Jzmlwj> undoJzmlwjList = jzmlwjlist;
                    List<String> wjxhList = jzmlwjlist.stream().map(Jzmlwj::getWjxh).collect(Collectors.toList());
                    List<OcrFile> existFiles = ocrFileService.listByBsbhWjxh(bsbh, wjxhList);

                    List<Long> existFileIds = new ArrayList<>();
                    if (CollectionUtils.isNotEmpty(existFiles)) {
                        // TODO 清理历史识别结果fdfs文件
                        existFileIds = existFiles.stream().map(OcrFile::getId).collect(Collectors.toList());
                        List<String> existWjxhList = existFiles.stream().map(OcrFile::getWjxh).collect(
                                Collectors.toList());
                        undoJzmlwjList = jzmlwjlist.stream().filter(jzmlwj -> !existWjxhList.contains(jzmlwj.getWjxh()))
                                                   .collect(Collectors.toList());
                        logger.warn(">>>>>>> 数据库中已经存在的识别文件数据{}条, existFileIds: {}, existWjxhList: {}",
                                    CollectionUtils.size(existFileIds), JSONUtil.toStrDefault(existFileIds),
                                    JSONUtil.toStrDefault(existWjxhList));
                    }
                    logger.info("过滤出需要未处理过的文件{}条, wjxhList: {}", CollectionUtils.size(undoJzmlwjList),
                                undoJzmlwjList.stream().map(Jzmlwj::getWjxh).collect(Collectors.joining(", ")));

                    // 批量保存文件信息
                    stopWatch.start("新数据入库");
                    Long finalTaskId = taskId;
                    List<OcrFile> ocrFileList = undoJzmlwjList.stream().map(file -> {
                        OcrFile ocrFile = new OcrFile();
                        ocrFile.setCreateTime(new Date());
                        ocrFile.setUpdateTime(new Date());
                        ocrFile.setOcrTaskId(finalTaskId);
                        ocrFile.setStatus(OcrFileStatusEnum.INIT.toString());
                        ocrFile.setType(TaskFileTypeEnum.BATCH.toString());
                        ocrFile.setBsbh(batchOcrVO.getBsbh());
                        ocrFile.setTaskid(batchOcrVO.getTaskid());
                        ocrFile.setWjxh(file.getWjxh());
                        ocrFile.setState(Constants.STATE_NOT_DELETED);
                        ocrFile.setBmsah(StringUtils.isEmpty(file.getBmsah()) ? batchOcrVO.getBmsah() : file.getBmsah());
                        return ocrFile;
                    }).collect(Collectors.toList());

                    if (CollectionUtils.isNotEmpty(ocrFileList)) {
                        logger.info("批量保存 ocr 待识别文件信息, taskId: {}, size: {}", taskId, CollectionUtils.size(ocrFileList));
                        ocrFileService.saveBatch(ocrFileList);
                    }
                    // 任务文件 id 放入队列, 异步处理每个文件识别
                    List<Long> fileIdList = ocrFileList.stream().filter(f -> Objects.nonNull(f.getId())).map(OcrFile::getId)
                                                       .collect(Collectors.toList());
                    fileIdList.addAll(existFileIds);

                    //构建指标统计数据
                    {
                        Metric metricEntity = JSONObject.toJavaObject(metricJson, Metric.class);
                        List<Metric> metricList = ocrFileList.stream().map(file -> {
                            Metric metric = new Metric();
                            metric.setDataId(Constants.OCR_METRIC_DATA_PREFIX + file.getId());
                            metric.setCallerIp(metricEntity.getCallerIp());
                            metric.setCreateTime(metricEntity.getCreateTime());
                            metric.setInterfaceName(metricEntity.getInterfaceName());
                            metric.setMrtricType(metricEntity.getMrtricType());
                            metric.setServiceIp(metricEntity.getServiceIp());
                            metric.setSystemId(metricEntity.getSystemId());
                            metric.setFileId(file.getId());
                            metric.setWjxh(file.getWjxh());
                            metric.setOcrStartTime(metricEntity.getOcrStartTime());
                            metric.setStartMillis(metricEntity.getOcrStartTime().getTime());
                            metric.setDwbm(metricEntity.getDwbm());
                            metric.setAjlbbm(metricEntity.getAjlbbm());
                            metric.setBsbh(metricEntity.getBsbh());
                            metric.setDwbm(batchOcrVO.getDwbm());
                            metric.setTaskid(batchOcrVO.getTaskid());
                            metric.setRemark(metricEntity.getRemark());
                            metric.setDwbm(metricEntity.getDwbm());
                            metric.setSystemId(metricEntity.getSystemId());
                            metric.setBmsah(StringUtils.isEmpty(file.getBmsah()) ? batchOcrVO.getBmsah() : file.getBmsah());
                            return metric;
                        }).collect(Collectors.toList());
                        metricService.saveBatch(metricList);
                    }

                    stopWatch.start("发送消息");
                    logger.info("发送OCR识别消息, fileIdList: {}", JSONUtil.toStrDefault(fileIdList));
                    messageProducer.sendOcrFileProcessMessage(fileIdList);
                    // 更新任务状态
                    ocrTaskService.updateStatus(taskId, OcrTaskStatusEnum.PROCESSING);

                    logger.info("批量OCR识别异步提交任务结束, batchOcrVO: {}, ocrExecutor: {}, stopWatch: {}", batchOcrVO,
                                ThreadPoolUtils.poolInfo(ocrExecutor), stopWatch);
                    return;
                }
                catch (Exception e) {
                    logger.warn("执行批量OCR识别异步任务异常", e);
                }
                finally {
                    lock.unlock();
                }
            }
        });
        return Result.success();
    }

    @Override
    public Result submitBatchRecognize(BatchOcrVO batchOcrVO, JSONObject obj) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("等待线程池执行");
        // 需要请求卷宗目录, 数据入库等操作, 为避免接口卡顿, 使用线程池处理
        logger.info("提交批量文件图像识别任务, batchOcrVO: {}, recognizeExecutor: {}", batchOcrVO,
                    ThreadPoolUtils.poolInfo(recognizeExecutor));
        recognizeExecutor.submit(new TLogInheritableTask() {
            @Override
            public void runTask() {
                stopWatch.start("获取分布式锁");
                logger.info("开始处理批量文件图像识别任务, bsbh: {}, taskid: {}, recognizeExecutor: {}", batchOcrVO.getBsbh(), batchOcrVO.getTaskid(),
                            ThreadPoolUtils.poolInfo(recognizeExecutor));
                //加锁
                String bsbh = batchOcrVO.getBsbh();
                RLock lock = redissonClient.getLock("Recognize_Submit_Lock_"+ bsbh);
                try {
                    int lockMill = PropUtils.getInt("submit_lock_time", 10) * 1000;
                    if (!lock.tryLock((long) Math.ceil(lockMill / 2f), lockMill, TimeUnit.MILLISECONDS)) {
                        logger.warn("无法获取锁, 当前图像识别任务在进行，bsbh: {}, stopWatch: {}", bsbh, stopWatch);
                        return;
                    }

                    logger.info("获取到锁，开始执行批量图像识别异步任务. taskVO: {}", batchOcrVO);
                    Long taskId = null;
                    List<RecognizeTask> recognizeTaskList = recognizeTaskService.listByBsbh(bsbh);
                    if (CollectionUtils.isNotEmpty(recognizeTaskList)) {
                        logger.info("图像识别任务标识编号bsbh: {} 存在历史记录: {}", bsbh, JSONUtil.toStrDefault(recognizeTaskList));
                        RecognizeTask existTask = recognizeTaskList.get(0);
                        taskId = existTask.getId();
                        logger.info("将其状态重置为INIT, bsbh: {}, taskId: {}", bsbh, taskId);
                        boolean b = recognizeTaskService.updateStatus(existTask.getId(), RecognizeTaskStatusEnum.INIT);
                    }
                    else {
                        // 保存任务信息到 db
                        taskId = recognizeTaskService.create(batchOcrVO.getSystemid(), batchOcrVO.getDwbm(),
                                                             batchOcrVO.getBsbh(), batchOcrVO.getTaskid(),
                                                             batchOcrVO.getBmsah());
                        logger.info("创建新Recognize识别任务, bsbh: {}, taskId: {}", bsbh, taskId);
                    }

                    stopWatch.start("swx:获取电子卷宗目录");
                    Result<JzmlInfo> result = requestHelper.getDZJZInfo(batchOcrVO.getBsbh());

                    stopWatch.start("swx:获取电子卷宗目录结果解析");
                    List<Jzmlwj> jzmlwjlist = null;
                    if (result.isSuccess()) {
                        jzmlwjlist = Optional.ofNullable(result).map(Result::getData).map(JzmlInfo::getData).map(
                                                     JzmlInfoResult::getJzmlwj).orElse(new ArrayList<>()).stream().filter(Objects::nonNull)
                                             .filter(wj -> StringUtils.isNotBlank(wj.getWjxh())).collect(
                                        Collectors.toList());
                    }
                    if (CollectionUtils.isEmpty(jzmlwjlist)) {
                        logger.info("未能获取到卷宗目录文件列表, 任务异常结束. {}", stopWatch);
                        ocrTaskService.abnormalEnd(taskId, "卷宗目录文件列表为空");
                        return;
                    }
                    logger.debug("图像识别-获取电子卷宗目录文件, 返回数据: {}条", CollectionUtils.size(jzmlwjlist));

                    stopWatch.start("筛选记录");
                    //筛选库中不存在记录
                    List<Jzmlwj> undoJzmlwjList = jzmlwjlist;
                    List<Long> existFileIds = new ArrayList<>();
                    List<String> wjxhList = jzmlwjlist.stream().map(Jzmlwj::getWjxh).filter(StringUtils::isNotBlank)
                                                      .collect(Collectors.toList());
                    List<RecognizeFile> existFiles = recognizeFileService.listByBsbhWjxh(bsbh, wjxhList);
                    if (CollectionUtils.isNotEmpty(existFiles)) {
                        existFileIds = existFiles.stream().map(RecognizeFile::getId).collect(Collectors.toList());

                        List<String> existWjxhList = existFiles.stream().map(RecognizeFile::getWjxh).collect(
                                Collectors.toList());
                        undoJzmlwjList = jzmlwjlist.stream().filter(jzmlwj -> !existWjxhList.contains(jzmlwj.getWjxh()))
                                                   .collect(Collectors.toList());
                    }

                    // 批量保存文件信息
                    Date now = new Date();
                    Long finalTaskId = taskId;
                    List<RecognizeFile> recognizeFileList = undoJzmlwjList.stream().map(file -> {
                        RecognizeFile recognizeFile = new RecognizeFile();
                        recognizeFile.setCreateTime(now);
                        recognizeFile.setUpdateTime(now);
                        recognizeFile.setRecognizeTaskId(finalTaskId);
                        recognizeFile.setStatus(RecognizeFileStatusEnum.INIT.toString());
                        recognizeFile.setType(TaskFileTypeEnum.BATCH.toString());
                        recognizeFile.setBsbh(batchOcrVO.getBsbh());
                        recognizeFile.setTaskid(batchOcrVO.getTaskid());
                        recognizeFile.setWjxh(file.getWjxh());
                        recognizeFile.setBmsah(StringUtils.isEmpty(file.getBmsah()) ? batchOcrVO.getBmsah() : file.getBmsah());
                        recognizeFile.setJzbh(recognizeFile.getJzbh());
                        recognizeFile.setMlbh(recognizeFile.getMlbh());
                        recognizeFile.setState(Constants.STATE_NOT_DELETED);
                        return recognizeFile;
                    }).collect(Collectors.toList());
                    stopWatch.start("数据入库");
                    logger.info("批量保存 图像识别 待处理文件信息, taskId: {}, size: {}", taskId, CollectionUtils.size(recognizeFileList));
                    recognizeFileService.saveBatch(recognizeFileList);

                    //构建指标统计数据
                    {
                        Metric metricEntity = JSONObject.toJavaObject(obj, Metric.class);
                        List<Metric> metricList = recognizeFileList.stream().map(file -> {
                            Metric metric = new Metric();
                            metric.setDataId(Constants.RECOG_METRIC_DATA_PREFIX + file.getId());
                            metric.setCallerIp(metricEntity.getCallerIp());
                            metric.setCreateTime(metricEntity.getCreateTime());
                            metric.setInterfaceName(metricEntity.getInterfaceName());
                            metric.setMrtricType(metricEntity.getMrtricType());
                            metric.setServiceIp(metricEntity.getServiceIp());
                            metric.setSystemId(metricEntity.getSystemId());
                            metric.setFileId(file.getId());
                            metric.setWjxh(file.getWjxh());
                            metric.setOcrStartTime(metricEntity.getOcrStartTime());
                            metric.setStartMillis(metricEntity.getOcrStartTime().getTime());
                            return metric;
                        }).collect(Collectors.toList());
                        metricService.saveBatch(metricList);
                    }

                    // 任务文件 id 放入队列, 异步处理每个文件识别
                    List<Long> fileIdList = recognizeFileList.stream().filter(f -> Objects.nonNull(f.getId())).map(
                            RecognizeFile::getId).collect(Collectors.toList());

                    logger.debug("数据库中已经存在的识别文件数据: {}", JSONUtil.toStrDefault(existFiles));
                    fileIdList.addAll(existFileIds);

                    stopWatch.start("发送消息");
                    logger.info("发送图像识别消息, fileIdList: {}", JSONUtil.toStrDefault(fileIdList));
                    messageProducer.sendRecognizeFileProcessMessage(fileIdList);

                    // 更新任务状态
                    recognizeTaskService.updateStatus(taskId, RecognizeTaskStatusEnum.PROCESSING);

                    return;
                }
                catch (Exception e) {
                    logger.warn("提交批量文件图像识别任务异常, batchOcrVO: {}", batchOcrVO, e);
                }
                finally {
                    logger.info("<-----释放锁-----> bsbh: {}", bsbh);
                    lock.unlock();
                    logger.info("提交批量文件图像识别任务结束, batchOcrVO: {}, recognizeExecutor: {}, stopWatch: {}", batchOcrVO,
                                ThreadPoolUtils.poolInfo(recognizeExecutor), stopWatch);
                }
            }
        });
        logger.info("提交批量文件图像识别任务完成, bsbh: {}, taskid: {}, recognizeExecutor: {}", batchOcrVO.getBsbh(),
                    batchOcrVO.getTaskid(), ThreadPoolUtils.poolInfo(recognizeExecutor));
        return Result.success();
    }

    @Override
    public Result<List<Wjtz>> recognize(byte[] fileBytes, String fileSuffix, List<RecognizeFuncEnum> funcList) {
        if (ArrayUtils.isEmpty(fileBytes) || CollectionUtils.isEmpty(funcList)) {
            return Result.failed(CommonResultEnum.PARAMETER_MISSING);
        }

        // 保存识别记录
        Long fileId = recognizeFileService.createSingle(funcList);

        // ocr 识别
        Result<String> ocrResult = doRecognize(fileId, null, fileBytes, String.valueOf(IdWokerUtil.nextId()));
        if (!ocrResult.isSuccess()) {
            return Result.from(ocrResult);
        }

        String ocrJson = ocrResult.getData();

        // 文件特征结果
        List<Wjtz> wjtzList = new ArrayList<>();
        for (RecognizeFuncEnum funcEnum : funcList) {
            wjtzList.addAll(funcHandler.handle(funcEnum, ocrJson));
        }

        // 更新任务状态
        recognizeFileService.updateStatus(fileId, RecognizeFileStatusEnum.END);
        Result<List<Wjtz>> result = Result.success(wjtzList);
        result.setFileId(fileId);
        return result;
    }

    private Result<String> doRecognize(Long fileId, String bsbh, byte[] fileBytes, String wjxh) {
        long start = System.currentTimeMillis();
        logger.info("开始单张图片图像识别, bsbh: {}, wjxh: {}, fileId: {}", bsbh, bsbh, fileId);
        // 生成 ocr 引擎请求参数
        JSONObject ocrParams = new JSONObject();

        ocrParams.put("pagetype", "page");
        ocrParams.put("resultlevel", "3");
        ocrParams.put("funclist", "ocr,er");
        Result<String> ocrResult = null;
        // ocr 识别
        String ocrJson = null;
        Path file = null;
        try {
            file = Files.createTempFile(wjxh, ".jpg");
            FileUtils.writeByteArrayToFile(file.toFile(), fileBytes);
            // 应用退出时删除文件, 兜底
            file.toFile().deleteOnExit();

            // 调用 ocr 引擎
            ocrResult = ocrClient.requestRestTemplate(file.toFile(), ocrParams);
            if (!ocrResult.isSuccess()) {
                recognizeFileService.abnormalEnd(fileId, "调用 ocr 引擎失败");
                return Result.failed(OcrResultEnum.OCR_RECOGNIZE_FAILED);
            }
            recognizeFileService.updateStatus(fileId, RecognizeFileStatusEnum.REQUEST_OCR_ENGINE);
            ocrJson = ocrResult.getData();
        }
        catch (Exception e) {
            logger.warn("ocr识别异常. fileId: {}", fileId, e);
            ocrFileService.abnormalEnd(fileId, "调用 ocr 引擎异常");
            return new Result<>(OcrResultEnum.OCR_RECOGNIZE_EXCEPTION);
        }
        finally {
            if (file.toFile() != null) {
                // 删除临时文件
                FileUtils.deleteQuietly(file.toFile());
            }
        }

        // 上传识别结果到 fastDFS
        byte[] bytes = ocrJson.getBytes(StandardCharsets.UTF_8);
        logger.info("上传图像识别结果到fdfs, size: {}", bytes == null ? 0 : bytes.length);
        try (InputStream is = new ByteArrayInputStream(bytes)) {
            String storePath = fdfsClient.uploadFile(is, bytes.length, OcrConstants.OCR_ENGINE_FDFS_SUFFIX);
            recognizeFileService.updateEngineResultPath(fileId, storePath);
        }
        catch (Exception e) {
            logger.warn("上传 ocr 识别结果到 fastDFS 异常. fileId: {}", fileId, e);
            recognizeFileService.abnormalEnd(fileId, "上传 ocr 识别结果到 fastDFS 异常");
            return Result.failed(OcrResultEnum.UPDATE_ENGINE_RESULT_FAILED);
        }

        logger.info("单张图片图像识别结束, bsbh: {}, wjxh: {}, fileId: {}, 耗时: {}ms", bsbh, bsbh, fileId,
                    System.currentTimeMillis() - start);
        return ocrResult;
    }

    @Override
    public Result pushOcrResult(Long ocrTaskId) {
        // 查询 task
        OcrTask ocrTask = ocrTaskService.getById(ocrTaskId);
        String taskStatus = ocrTask.getStatus();
        if (OcrTaskStatusEnum.PUSHING.name().equals(taskStatus) || OcrTaskStatusEnum.END.name().equals(taskStatus)) {
            return Result.success();
        }

        if (ocrTask.getFailNums() > maxFailNums) {
            return Result.failed("该task已经重试" + maxFailNums + "次!!!,task:" + ocrTaskId);
        }

        // 查询 ocrFile
        List<OcrFile> ocrFileList = ocrFileService.findByOcrTaskId(ocrTaskId);
        if (ocrFileList.isEmpty()) {
            return Result.failed("ocrTaskId: " + ocrTaskId + ", 关联的OcrFile为空, 不推送!!!");
        }

        JzInfoResult ocrResult = new JzInfoResult();
        ocrResult.setBsbh(ocrTask.getBsbh());
        ocrResult.setTaskid(ocrTask.getTaskid());

        if (CollectionUtils.isNotEmpty(ocrFileList)) {
            List<Jzmlwj> jzmlwjList = ocrFileList.stream().map(f -> {
                Jzmlwj jzmlwj = new Jzmlwj();
                jzmlwj.setWjxh(f.getWjxh());
                jzmlwj.setWjzdy("Y");
                //bmsah是逗号分隔的字符串
                jzmlwj.setBsbh(ocrTask.getBsbh());
                return jzmlwj;
            }).collect(Collectors.toList());
            ocrResult.setJzmlwjlist(jzmlwjList);
        }

        // 更新 task 状态完成
        ocrTaskService.updateStatus(ocrTaskId, OcrTaskStatusEnum.PUSHING);

        if (ocrResult.getJzmlwjlist() != null) {
            ocrResult.getJzmlwjlist().forEach(c -> {
                if (StringUtils.isEmpty(c.getFiletype())) {
                    c.setFiletype("1");
                }
            });
        }

        // 推送结果
        Result pushResult = requestHelper.saveOCRResult(ocrResult);

        if (!pushResult.isSuccess()) {
            ocrTaskService.updateTimes(ocrTaskId);
            messageProducer.sendOcrResultPushMessage(Lists.newArrayList(ocrTaskId));
            return Result.failed("推送失败: taskId: " + ocrTaskId);
        }


        // 更新 task 状态完成
        ocrTaskService.updateStatusAndPushInfo(ocrTaskId, OcrTaskStatusEnum.END, JSONUtil.toStrDefault(pushResult));

        logger.info("推送OCR识别结果消息处理结束, id: {}", ocrTaskId);
        return Result.success();
    }

    @Override
    public Result pushRecognizeResult(Long recognizeTaskId) {

        // 将识别完成消息，推送给中台
        // 1取出任务，2推送完成消息，3更新状态
        RecognizeTask recognizeTask = recognizeTaskService.getById(recognizeTaskId);
        if (recognizeTask.getFailNums() != null && recognizeTask.getFailNums() > maxFailNums) {
            return Result.failed("该task已经重试" + maxFailNums + "次!!!,task:" + recognizeTask);
        }
        boolean isRec = recognizeTaskService.pushSuccessMessage(recognizeTask);
        if (isRec) {
            recognizeTaskService.updateStatus(recognizeTaskId, RecognizeTaskStatusEnum.END);
            return Result.success("");
        } else {
            logger.info("图像识别结果推送失败, 重新发送推送消息, id: {}", recognizeTaskId);
            recognizeTaskService.updateTimes(recognizeTaskId);
            messageProducer.sendRecognizeResultPushMessage(Lists.newArrayList(recognizeTaskId));
            return Result.failed("识别结果推送失败");
        }
    }

    @Override
    public void processOcrTask(long ocrFileId) {
        StopWatch watch = new StopWatch("processOcrTask");

        logger.info("查询OcrFile, ocrFileId: {}", ocrFileId);
        watch.start("查询OcrFile");
        OcrFile ocrFile = ocrFileService.getById(ocrFileId);
        if (ocrFile == null) {
            logger.warn("OcrFile数据不存在, 任务结束. id: {}", ocrFileId);
            return;
        }

        // 标识编号
        String bsbh = ocrFile.getBsbh();
        // 任务id
        String taskid = ocrFile.getTaskid();
        // 文件序号
        String wjxh = ocrFile.getWjxh();
        logger.info("开始下载电子卷宗文件文件, wjxh: {}", wjxh);
        // 下载文件
        watch.start("下载电子卷宗文件");
        Result<DzjzDataDTO> fileContentResult = requestHelper.getDZJZJPG(bsbh, taskid, wjxh);
        if (!fileContentResult.isSuccess()) {
            // 文件下载失败, 该文件识别任务结束
            ocrFileService.abnormalEnd(ocrFileId, "文件下载失败");
            metricService.updateByDataId(Constants.OCR_METRIC_DATA_PREFIX + ocrFileId, CommonResultEnum.FAILED);
            return;
        }
        ocrFileService.updateStatus(ocrFileId, OcrFileStatusEnum.GOT_FILE_STREAM);

        // 二进制图片文件内容
        byte[] fileBytes = fileContentResult.getData().getData();

        saveFile2TmpDir(wjxh + ".jpg", fileBytes);

        // ocr 识别
        //metric.setOcrStartTime(new Date());
        watch.start("ocr 识别");
        Result<String> ocrResult = doOcr(ocrFileId, fileBytes, wjxh, bsbh);
        if (!ocrResult.isSuccess()) {
            metricService.updateByDataId(Constants.OCR_METRIC_DATA_PREFIX + ocrFileId, CommonResultEnum.FAILED);
            return;
        }

        // 合成pdf
        watch.start("合成双层pdf");
        String ocrJson = ocrResult.getData();
        Result<byte[]> composeResult = composePdf(ocrFileId, fileBytes, ocrJson);
        if (!composeResult.isSuccess()) {
            return;
        }

        byte[] pdfFileBytes = composeResult.getData();

        saveFile2TmpDir(wjxh + "_ocr.pdf", pdfFileBytes);

        // 推送pdf
        watch.start("推送pdf");
        DzjzDataDTO dataDTO = new DzjzDataDTO();
        dataDTO.setSuccess(true);
        dataDTO.setMessage(null);
        dataDTO.setCode(null);
        dataDTO.setParams(fileContentResult.getData().getParams());
        dataDTO.setDataLength(pdfFileBytes.length);
        dataDTO.setData(pdfFileBytes);
        Result pushResult = requestHelper.savePDF(dataDTO);
        if (!pushResult.isSuccess()) {
            logger.warn("推送双层 pdf 文件失败, fileId: {}, message: {}", ocrFileId, pushResult.getError());
            ocrFileService.abnormalEnd(ocrFileId, "推送双层 pdf 文件失败");
            metricService.updateByDataId(Constants.OCR_METRIC_DATA_PREFIX + ocrFileId, CommonResultEnum.FAILED);
            return;
        }
        ocrFileService.updateStatus(ocrFileId, OcrFileStatusEnum.END);
        metricService.updateByDataId(Constants.OCR_METRIC_DATA_PREFIX + ocrFileId, CommonResultEnum.SUCCESS);
        logger.info("OCR识别单文件处理完成, ocrFileId: {}, 耗时分布: {}", ocrFileId, watch);
    }


    @Override
    public void processRecognizeTask(long recognizeFileId) {
        StopWatch watch = new StopWatch("processRecognizeTask");

        logger.info("查询已存在RecognizeFile");
        watch.start("查询已存在RecognizeFile");
        RecognizeFile recognizeFile = recognizeFileService.getById(recognizeFileId);
        if (recognizeFile == null) {
            logger.warn("RecognizeFile数据不存在, 任务结束. id: {}", recognizeFileId);
            return;
        }

        String wjxh = recognizeFile.getWjxh();
        String bsbh = recognizeFile.getBsbh();

        List<OcrFile> existOcrFiles = ocrFileService.findByWjxh(Lists.newArrayList(wjxh));
        OcrFile existOcrFile = existOcrFiles.stream().filter(file -> StringUtils.isNotBlank(file.getEngineResultPath()))
                                       .findAny().orElse(null);
        //使用卷宗识别结果填充图像识别结果 减少OCR交互识别次数
        if (null != existOcrFile) {
            logger.info("使用已存在的OCR识别结果 existOcrFile:{}", existOcrFile);
            recognizeFileService.updateEngineResultPath(recognizeFileId, existOcrFile.getEngineResultPath());
        } else {
            watch.start("查询RecognizeFile ByWjxh");
            //metric.setStartMillis(System.currentTimeMillis());
            List<RecognizeFile> existFiles = recognizeFileService.listByBsbhWjxh(bsbh, Lists.newArrayList(wjxh));
            RecognizeFile existRecognizeFile = existFiles.stream().filter(file -> !file.getId().equals(recognizeFileId))
                                                     .filter(file -> StringUtils.isNotBlank(file.getEngineResultPath()))
                                                     .findAny().orElse(null);
            if (existRecognizeFile != null) {
                logger.info("使用已存在的图像识别结果, existRecognizeFile: {}", existRecognizeFile);
                recognizeFileService.updateEngineResultPath(recognizeFileId, existRecognizeFile.getEngineResultPath());
            } else {
                // 下载文件 todo 获取图片地址待确认，1.2.6 （04-1），此接口暂时无法获取，先用1.2.5 04 ocr 识别专用接口获取
                // 此接口需要taskid,图像识别无taskid,用bsbh替代
                watch.start("下载电子卷宗图片");
                Result<DzjzDataDTO> fileContentResult = requestHelper.getDZJZJPG(bsbh, bsbh, wjxh);
                if (!fileContentResult.isSuccess()) {
                    // 文件下载失败, 该文件识别任务结束
                    recognizeFileService.abnormalEnd(recognizeFileId, "电子卷宗图片下载失败");
                    metricService.updateByDataId(Constants.RECOG_METRIC_DATA_PREFIX + recognizeFileId, CommonResultEnum.FAILED);
                    return;
                }

                recognizeFileService.updateStatus(recognizeFileId, RecognizeFileStatusEnum.GOT_FILE_CONTENT);

                // 二进制图片文件内容
                byte[] fileBytes = fileContentResult.getData().getData();

                // ocr 识别
                watch.start("引擎识别");
                Result<String> ocrResult = doRecognize(recognizeFileId, bsbh, fileBytes, wjxh);
                if (!ocrResult.isSuccess()) {
                    metricService.updateByDataId(Constants.RECOG_METRIC_DATA_PREFIX + recognizeFileId, CommonResultEnum.FAILED);
                    return;
                }
            }
        }
        recognizeFileService.updateStatus(recognizeFileId, RecognizeFileStatusEnum.END);
        metricService.updateByDataId(Constants.RECOG_METRIC_DATA_PREFIX + recognizeFileId, CommonResultEnum.SUCCESS);
        logger.info("图像识别单文件处理完成, recognizeFileId: {}, 耗时: {}", recognizeFileId, watch);
    }

    /**
     * @param bmsah
     * @param bsbhlist
     * @param dwbm
     * @param jzbh
     */
    @Override
    public void relationShipTaskAndBMSAH(String bmsah, String[] bsbhlist, String dwbm, String jzbh) {
        // OCR识别任务
        LambdaQueryWrapper<OcrTask> ocrTaskQueryWrapper = new LambdaQueryWrapper<>();
        ocrTaskQueryWrapper.select(OcrTask::getId).in(OcrTask::getBsbh, bsbhlist);
        List<OcrTask> octTaskList = ocrTaskService.list(ocrTaskQueryWrapper);
        if (CollectionUtils.isNotEmpty(octTaskList)) {
            LambdaUpdateWrapper<OcrTask> ocrTaskUpdateWrapper = new LambdaUpdateWrapper<>();
            List<Long> ids = octTaskList.stream().map(OcrTask::getId).collect(Collectors.toList());
            ocrTaskUpdateWrapper.in(OcrTask::getId, ids).set(OcrTask::getBmsah, bmsah).set(OcrTask::getDwbm, dwbm);

            logger.info("更新OcrTask数据的bmsah, ids: {}", ids);
            ocrTaskService.update(ocrTaskUpdateWrapper);

            // 再更新任务下的文件集合
            LambdaUpdateWrapper<OcrFile> fileUpdateWrapper = new LambdaUpdateWrapper<>();
            fileUpdateWrapper.in(OcrFile::getOcrTaskId, ids).set(OcrFile::getBmsah, bmsah);

            logger.info("更新OcrFile数据的bmsah, ocrTaskIds: {}", ids);
            ocrFileService.update(fileUpdateWrapper);
        }

        // 图像识别任务也要关联部门受案号
        LambdaQueryWrapper<RecognizeTask> recognizeTaskQueryWrapper = new LambdaQueryWrapper<>();
        recognizeTaskQueryWrapper.select(RecognizeTask::getId).in(RecognizeTask::getBsbh, bsbhlist);
        List<RecognizeTask> recognizeTaskList = recognizeTaskService.list(recognizeTaskQueryWrapper);
        if (CollectionUtils.isNotEmpty(recognizeTaskList)) {
            LambdaUpdateWrapper<RecognizeTask> recognizeTaskUpdateWrapper = new LambdaUpdateWrapper<>();
            List<Long> ids = recognizeTaskList.stream().map(RecognizeTask::getId).collect(Collectors.toList());
            recognizeTaskUpdateWrapper.in(RecognizeTask::getId, ids).set(RecognizeTask::getBmsah, bmsah).set(RecognizeTask::getDwbm, dwbm);

            logger.info("更新RecognizeTask数据的bmsah, ids: {}", ids);
            recognizeTaskService.update(recognizeTaskUpdateWrapper);

            // 再更新任务下的文件集合
            LambdaUpdateWrapper<RecognizeFile> fileUpdateWrapper = new LambdaUpdateWrapper<>();
            fileUpdateWrapper.in(RecognizeFile::getRecognizeTaskId, ids).set(RecognizeFile::getBmsah, bmsah);

            logger.info("更新RecognizeFile数据的bmsah, recognizeTaskIds: {}", ids);
            recognizeFileService.update(fileUpdateWrapper);
        }
    }

    @Override
    public Wjsbjg recognize(OcrResultRequestVo ocrParam) {
        // 文件编号
        String wjbh = ocrParam.getWjbh();
        // 是否输出文件内容（双层pdf）
        boolean sfwjnr = ocrParam.isSfwjnr();
        // 是否输出文件文本
        boolean sfwjwb = ocrParam.isSfwjwb();
        // 是否输出文件结构化信息（带坐标）
        boolean sfwjxx = ocrParam.isSfwjxx();
        // 文件内容
        byte[] fileBytes = Base64.decodeBase64(ocrParam.getWjnr());

        Wjsbjg wjsbjg = new Wjsbjg();
        wjsbjg.setWjbh(wjbh);

        // 获取每一页的 ocr 识别结果
        Map<String, String> ocrEngineParams = buildOcrParams(ocrParam);
        List<PageOcrInfo> pageOcrInfoList = ocrForEachPage(wjbh, fileBytes, ocrParam.getWjhz(), ocrEngineParams);
        // 设置文件页数
        wjsbjg.setWjys(CollectionUtils.size(pageOcrInfoList));

        // 构造文件识别信息
        List<com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx> wjsbxxList = buildWjsbxxList(pageOcrInfoList,
                sfwjnr, sfwjwb, sfwjxx);
        wjsbjg.setWjsbxx(wjsbxxList);

        // 文件内容
        if (sfwjnr) {
            String pdfnr = null;
            String pdfMD5 = null;
            // 如果是单页则直接取单页的文件内容
            if (wjsbjg.getWjys() == 1) {
                com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx wjsbxx = wjsbjg.getWjsbxx().get(0);
                pdfnr = wjsbxx.getWjnr();
                pdfMD5 = DigestUtils.md5DigestAsHex(wjsbxx.getPdfBytes());
                // md5 计算结束, 清空二进制文件内容, 节省资源
                wjsbxx.setPdfBytes(null);
            } else {
                List<byte[]> pdfByteList = wjsbjg.getWjsbxx().stream()
                        .map(com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx::getPdfBytes)
                        .filter(ArrayUtils::isNotEmpty).collect(Collectors.toList());
                // 多页合并
                byte[] mergedPdfBytes = new com.iflytek.icourt.common.util.PdfComposeUtil().mergePdfList(pdfByteList);
                pdfMD5 = DigestUtils.md5DigestAsHex(mergedPdfBytes);
                pdfnr = Base64.encodeBase64String(mergedPdfBytes);

                // 清空单页文件的二进制文件内容, 节省资源
                wjsbjg.getWjsbxx().stream().forEach(x -> x.setPdfBytes(null));
            }

            wjsbjg.setPdfnr(pdfnr);
            wjsbjg.setWjmd5(pdfMD5);
            wjsbjg.setWjhz(".pdf");
            logger.info("文件内容生成完成, wjbh: {}", wjbh);
        }

        // 文件文本
        if (sfwjwb) {
            // 遍历文件识别信息, 拼接出完整的文件文本内容
            String wjwb = wjsbjg.getWjsbxx().stream()
                    .map(com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx::getWjwb)
                    .collect(Collectors.joining(""));
            wjsbjg.setTxtnr(wjwb);
            logger.info("文件文本生成完成, wjbh: {}", wjbh);
        }
        return wjsbjg;
    }

    /**
     * 构造 ocr 识别请求参数
     */
    private Map<String, String> buildOcrParams(OcrResultRequestVo ocrParam) {
        boolean sfjp = ocrParam.isSfjp();
        boolean sfxz = ocrParam.isSfxz();
        boolean sfwjnr = ocrParam.isSfwjnr();
        boolean sfwjxx = ocrParam.isSfwjxx();
        Map<String, String> params = new HashMap<>();
        params.put("pagetype", "page");

        // 如果需要纠偏和旋转检查, 需要开启引擎朝向检测功能
        String funclist = "ocr,er";
        if (sfjp || sfxz) {
            funclist += ",od";
        }
        params.put("funclist", "");

        // 当需要输出双层 PDF 或 返回文件结构时, 需要引擎返回单字定位信息
        String level = "1";
        if (sfwjnr || sfwjxx) {
            level = "3";
        }
        params.put("resultlevel", level);
        return params;
    }

    /**
     * 构造文件识别信息集合
     */
    private List<com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx> buildWjsbxxList(
            List<PageOcrInfo> pageOcrInfoList, boolean sfwjnr, boolean sfwjwb, boolean sfwjxx) {
        int infoSize = pageOcrInfoList.size();

        if (infoSize == 1) {
            return Lists.newArrayList(buildWjsbxx(pageOcrInfoList, 0, sfwjxx, sfwjwb, sfwjnr));
        } else {
            // 多页时使用多线程处理
            try {
                com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx[] wjsbxxArray = new com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx[infoSize];
                final CountDownLatch countDownLatch = new CountDownLatch(infoSize);
                for (int i = 0; i < infoSize; i++) {
                    int finalI = i;
                    ocrCompoeExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx wjsbxx = buildWjsbxx(
                                        pageOcrInfoList, finalI, sfwjxx, sfwjwb, sfwjnr);
                                wjsbxxArray[finalI] = wjsbxx;
                            } catch (Exception e) {
                                logger.warn("构造单页文件识别信息异常. pageIndex: {}", finalI, e);
                            } finally {
                                countDownLatch.countDown();
                            }
                        }
                    });
                }

                countDownLatch.await();
                logger.info("构造文件识别信息集合完成");
                return Lists.newArrayList(wjsbxxArray);
            } catch (InterruptedException e) {
                logger.warn("构造文件识别信息集合异常", e);
                return Lists.newArrayList();
            }
        }
    }

    /**
     * 构造文件识别信息
     */
    private com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx buildWjsbxx(List<PageOcrInfo> pageOcrInfoList,
                                                                                 int pageIndex, boolean sfwjxx, boolean sfwjwb, boolean sfwjnr) {
        // 并发问题修复
        logger.info("页面ocr信息集合:" + pageOcrInfoList.size());
        PageOcrInfo pageOcrInfo = pageOcrInfoList.get(pageIndex);
        com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx wjsbxx = new com.iflytek.jzcpx.procuracy.ocr.entity.swx.result.Wjsbxx();
        wjsbxx.setWjkd(pageOcrInfo.getKd());
        wjsbxx.setWjgd(pageOcrInfo.getGd());
        wjsbxx.setWjyskd(pageOcrInfo.getYskd());
        wjsbxx.setWjysgd(pageOcrInfo.getYsgd());
        wjsbxx.setWjqxjd(0);
        wjsbxx.setWjpzj(0);
        wjsbxx.setYssb(pageOcrInfo.getYskd() > pageOcrInfo.getKd() ? "1" : "");

        // 文件结构信息
        if (sfwjxx) {
            Wjjgxx wjjgxx = buildWjjgxx(pageOcrInfo.getOcrData());
            wjsbxx.setWjjgxx(wjjgxx);
            logger.debug("构造单页文件结构信息操作完成. pageIndex: {}", pageIndex);
        }

        // 单页文件文本
        if (sfwjwb) {
            StringBuilder sb = new StringBuilder();
            // 如果输出文件结构信息, 则直接从结构化数据中获取文本内容
            if (sfwjxx && wjsbxx.getWjjgxx() != null) {
                List<Wbxx> wbxxes = wjsbxx.getWjjgxx().getWbxx();
                if (CollectionUtils.isNotEmpty(wbxxes)) {
                    for (Wbxx wbxx : wbxxes) {
                        sb.append(wbxx.getHwb());
                    }
                }

            } else {
                String ocrData = pageOcrInfo.getOcrData();
                if (StringUtils.isNotBlank(ocrData)) {
                    String text = OcrUtil.getTextByType(ocrData,
                            com.iflytek.jzcpx.procuracy.tools.ocr.OcrConstants.STR_TWO);
                    sb.append(text);
                }
            }

            String wjwb = sb.toString();
            wjsbxx.setWjwb(wjwb);
            logger.debug("输出单页文件文本操作完成. pageIndex: {}", pageIndex);
        }

        // 单页文件内容
        if (sfwjnr) {
            List<byte[]> imgByteList = Lists.newArrayList(pageOcrInfo.getImgBytes());
            List<String> ocrResultList = Lists.newArrayList(pageOcrInfo.getOcrData());
            // 合成双层 PDF
            byte[] pdfBytes = exportPdf(imgByteList, ocrResultList);
            wjsbxx.setPdfBytes(pdfBytes);
            logger.debug("合成pdf完成");

            // pdf 内容转 base64
            String pdfBase64Str = Base64.encodeBase64String(pdfBytes);
            wjsbxx.setWjnr(pdfBase64Str);
            logger.debug("输出单页文件内容操作完成, pageIndex: {}", pageIndex);
        }

        logger.debug("构造单页文件识别信息完成, pageIndex: {}", pageIndex);
        return wjsbxx;
    }

    /**
     * 拆分(如果是 pdf)并ocr识别每页图片
     */
    private List<PageOcrInfo> ocrForEachPage(String wjbh, byte[] fileBytes, String wjhz,
                                             Map<String, String> ocrEngineParams) {
        List<PageOcrInfo> pageOcrInfoList = new CopyOnWriteArrayList();
//		List<PageOcrInfo> pageOcrInfoList = new ArrayList<>();
        wjhz = wjhz.toLowerCase();
        switch (wjhz) {
            case ".jpg":
            case ".png":
                // 图片压缩识别
                PageOcrInfo pageOcrInfo = compressAndOcr(wjbh, fileBytes, wjhz, ocrEngineParams);
                pageOcrInfoList.add(pageOcrInfo);
                logger.info("对单张图片进行压缩/ocr识别处理完成, wjbh: {}", wjbh);
                break;
            case ".pdf":
                /**
                 * PDDocument doc = null; try (InputStream inputStream = new
                 * ByteArrayInputStream(fileBytes)) { // 读取 pdf 文件 doc =
                 * PDDocument.load(inputStream);
                 *
                 * // 对每页进行识别 int pageCount = doc.getNumberOfPages(); PDFRenderer renderer = new
                 * PDFRenderer(doc); if (pageCount == 1) { PageOcrInfo subPageOcrInfo =
                 * ocrSubPage(renderer, 0, wjbh, ocrEngineParams); pageOcrInfoList =
                 * Lists.newArrayList(subPageOcrInfo); } else { // 多页时并发拆分和识别 final
                 * PageOcrInfo[] PageOcrInfoArray = new PageOcrInfo[pageCount]; final
                 * CountDownLatch countDownLatch = new CountDownLatch(pageCount); for (int i =
                 * 0; i < pageCount; i++) { int finalI = i; ocrCompoeExecutor.execute(new
                 * Runnable() {
                 *
                 * @Override public void run() { try { PageOcrInfo subPageOcrInfo =
                 *           ocrSubPage(renderer, finalI, wjbh, ocrEngineParams);
                 *           PageOcrInfoArray[finalI] = subPageOcrInfo; } finally {
                 *           countDownLatch.countDown(); } } }); }
                 *
                 *           countDownLatch.await(); pageOcrInfoList =
                 *           Lists.newArrayList(PageOcrInfoArray); }
                 *
                 *           logger.info("对 PDF 文件进行 ocr 识别处理完成, wjbh: {}", wjbh); } catch
                 *           (Exception e) { logger.info("OCR识别处理PDF文件异常, wjbh: {}", wjbh, e); }
                 */
                try {
                    List<File> pdfToImages = gsUtil.pdf2Image(wjbh, fileBytes, "", "pdf", "jpg", null);
                    int pageCount = pdfToImages.size();
                    if (pageCount == 1) {
                        File file = pdfToImages.get(0);
                        FileInputStream inputstream = new FileInputStream(file);
                        byte[] b = toByteArray(inputstream);
                        PageOcrInfo subPageOcrInfo = ocrSubPage(b, wjbh, ocrEngineParams);
                        pageOcrInfoList = Lists.newArrayList(subPageOcrInfo);
                        inputstream.close();
                    } else {
                        // 多页时并发拆分和识别
                        final PageOcrInfo[] PageOcrInfoArray = new PageOcrInfo[pageCount];
                        Map<Integer, PageOcrInfo> PageOcrInfoMap = new ConcurrentHashMap<>(pageCount);
                        final CountDownLatch countDownLatch = new CountDownLatch(pageCount);
                        for (int i = 0; i < pageCount; i++) {
                            int finalI = i;
                            ocrCompoeExecutor.execute(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        File file = pdfToImages.get(finalI);
                                        FileInputStream inputstream = new FileInputStream(file);
                                        byte[] b = toByteArray(inputstream);

                                        PageOcrInfo subPageOcrInfo = ocrSubPage(b, wjbh, ocrEngineParams);
                                        PageOcrInfoMap.put(finalI, subPageOcrInfo);
//								PageOcrInfoArray[finalI] = subPageOcrInfo;
                                        logger.info("单个OCR:{}", StringUtils.isNotBlank(subPageOcrInfo.getOcrData()));
                                        inputstream.close();
                                    } catch (Exception e) {
                                        logger.info("OCR识别处理PDF文件异常, finalI: {}", finalI, e);
                                    } finally {
                                        countDownLatch.countDown();
                                    }
                                }
                            });
                        }

                        countDownLatch.await();

                        Map<Integer, PageOcrInfo> result = PageOcrInfoMap.entrySet().stream()
                                .sorted(Map.Entry.comparingByKey())
                                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                                        (oldValue, newValue) -> oldValue, ConcurrentHashMap::new));
                        for (Integer pageIndex : result.keySet()) {
                            logger.info("pageIndex  is :{}", pageIndex);
                            pageOcrInfoList.add(PageOcrInfoMap.get(pageIndex));
                        }

//				pageOcrInfoList = Lists.newArrayList(PageOcrInfoArray);
                    }

                    // 删除临时文件
                    File parentFile = null;
                    for (File file : pdfToImages) {
                        if (null == parentFile) {
                            parentFile = file.getParentFile();
                        }
                        FileUtils.deleteQuietly(file);
                    }
                    if (null != parentFile && parentFile.exists()) {
                        parentFile.delete();
                    }
                    if (null != parentFile.getParentFile() && parentFile.getParentFile().exists()) {
                        parentFile.getParentFile().delete();
                    }
                } catch (Exception e) {
                    logger.info("OCR识别处理PDF文件异常, wjbh: {}", wjbh, e);
                }
                break;
            default:
                throw new InvalidParameterException(CommonResultEnum.INVALID_PARAMETER, "不支持该文件后缀名");
        }
        return pageOcrInfoList;
    }

    public byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024 * 4];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }

    /**
     * 拆分pdf的某一页, 然后压缩并ocr
     */
    private PageOcrInfo ocrSubPage(PDFRenderer renderer, int pageIndex, String wjbh,
                                   Map<String, String> ocrEngineParams) {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            // 拆分成图片
            BufferedImage image = null;
            synchronized (renderer) {
                image = renderer.renderImage(pageIndex, 1f);
            }
            ImageIO.write(image, "png", outputStream);
            byte[] imgBytes = outputStream.toByteArray();
            // saveFile("www.png", imgBytes);
            logger.debug("PDF子页面拆分完成, pageIndex: {}", pageIndex);

            // 图片压缩识别
            PageOcrInfo subPageOcrInfo = compressAndOcr(wjbh, imgBytes, ".png", ocrEngineParams);
            logger.debug("对PDF子页面进行压缩/ocr识别处理完成, pageIndex: {}", pageIndex);
            return subPageOcrInfo;
        } catch (Exception e) {
            logger.warn("对PDF子页面进行拆分/压缩/ocr识别处理异常, pageIndex: {}", pageIndex, e);
            return null;
        }
    }

    /**
     * 处理PDF某一页切割的图片, 然后压缩并ocr
     */
    private PageOcrInfo ocrSubPage(byte[] imgBytes, String wjbh, Map<String, String> ocrEngineParams) {
        try {
            // saveFile("www.png", imgBytes);
            // 图片压缩识别
            PageOcrInfo subPageOcrInfo = compressAndOcr(wjbh, imgBytes, ".png", ocrEngineParams);
            logger.debug("对PDF子页面进行压缩/ocr识别处理完成");
            return subPageOcrInfo;
        } catch (Exception e) {
            logger.warn("对PDF子页面进行拆分/压缩/ocr识别处理异常, {}", e);
            return null;
        }
    }

    public void saveFile2TmpDir(String filename, byte[] data) {
        boolean saveDZJZ = PropUtils.getBool("saveDZJZ", false);
        if (!saveDZJZ || data == null || data.length == 0) {
            return;
        }
        try {
            logger.debug("将电子卷宗图片/PDF写入本地磁盘, filename: {}, size: {}", filename, data == null ? 0 : data.length);
            if (data != null) {
                String path = FileUtils.getTempDirectoryPath();
                String filePath = StringUtils.appendIfMissing(path, File.separator) + filename;
                logger.debug("将电子卷宗图片/PDF写入磁盘文件为:{}, size: {}", filePath, data.length);

                FileUtils.writeByteArrayToFile(new File(filePath), data);
            }
        }
        catch (Exception e) {
            logger.warn("文件写入本地临时目录异常", e);
        }
        logger.debug("将电子卷宗写入本地磁盘结束");
    }

    /**
     * 构造文件结构信息对象
     */
    private Wjjgxx buildWjjgxx(String ocrData, Wjsbxx wjsbxx) {
        Wjjgxx wjjgxx = new Wjjgxx();

        com.iflytek.icourt.model.pdf.OcrResult ocrResult = null;
        try {
            ocrResult = JSONObject.parseObject(ocrData, com.iflytek.icourt.model.pdf.OcrResult.class);
            if (ocrResult == null || ocrResult.getTaskResult() == null
                    || CollectionUtils.isEmpty(ocrResult.getTaskResult().getPage())) {
                logger.info("ocr识别结果为null, 构造文件结构信息对象结束");
                return wjjgxx;
            }
        } catch (Exception e) {
            logger.warn("构造文件结构对象失败, ocr 结果JSON转成Bean异常, 构造文件结构信息对象结束. ocrData: {}", ocrData, e);
            return wjjgxx;
        }

        List<Page> pageList = ocrResult.getTaskResult().getPage();
        List<Textline> textlineList = new ArrayList<>();
        // 获取识别结果中的文本区域
        for (Page page : pageList) {
            if (page == null) {
                continue;
            }

            // 先取文本区内的文本行, page -> textRegion -> texline
            if (CollectionUtils.isNotEmpty(page.getTextRegion())) {
                List<Textline> textlines = page.getTextRegion().parallelStream().filter(Objects::nonNull)
                        .map(TextRegion::getTextline).filter(CollectionUtils::isNotEmpty).flatMap(t -> t.stream())
                        .filter(Objects::nonNull).collect(Collectors.toList());
                CollectionUtils.addAll(textlineList, textlines);
            }

            // 再取表格区的文本行, page -> table -> cell -> textline
            if (CollectionUtils.isNotEmpty(page.getTable())) {
                List<Textline> textlines = page.getTable().stream().filter(Objects::nonNull).map(Table::getCell)
                        .parallel().filter(CollectionUtils::isNotEmpty).flatMap(c -> c.stream())
                        .filter(Objects::nonNull).map(Cell::getTextline).filter(CollectionUtils::isNotEmpty)
                        .flatMap(t -> t.stream()).filter(Objects::nonNull).collect(Collectors.toList());
                CollectionUtils.addAll(textlineList, textlines);
            }
            // 文件高度 宽度
            Rect rect = page.getRect();
            if (null != rect) {
                wjsbxx.setWjgd(rect.getH());
                wjsbxx.setWjkd(rect.getW());
            }
            // 文件倾斜角度
            Double wjqxjd = page.getAngle();
            if (null != wjqxjd) {
                wjsbxx.setWjqxjd(String.valueOf(wjqxjd));
            }
            // 文件偏转角度
            int wjpzjd = page.getOrientation();
            wjsbxx.setWjpzjd(wjpzjd);
        }
        if (CollectionUtils.isEmpty(textlineList)) {
            logger.info("ocr识别结果中没有文本行, 构造文件结构信息对象结束");
            return wjjgxx;
        }

        // 按 y 轴进行文本行排序
        textlineList.sort(Comparator.comparing(Textline::getRect,
                Comparator.comparing(Rect::getY, Comparator.comparingInt(y -> y))));

        // 生成文本信息
        List<Wbxx> wbxxList = buildWbxx(textlineList);
        wjjgxx.setWbxx(wbxxList);
        wjjgxx.setWbsl(CollectionUtils.size(wbxxList));

        // 提取出所有的手写体文本行
        List<Textline> handwriteTextLines = textlineList.stream()
                .filter(l -> OCR_HANDWRITE_TYPE.equalsIgnoreCase(l.getType()))
                .filter(l -> CollectionUtils.isNotEmpty(l.getSent()))
                .sorted(Comparator.comparing(Textline::getRect,
                        Comparator.comparing(Rect::getY, Comparator.comparingInt(y -> y))))
                .collect(Collectors.toList());
        // 生成手写信息
        List<Sxxx> sxxxList = buildSxxx(handwriteTextLines);
        wjjgxx.setSxxx(sxxxList);
        wjjgxx.setSxsl(CollectionUtils.size(sxxxList));

        logger.info("文件结构信息构造完成");
        return wjjgxx;
    }

    /**
     * 构造文件结构信息对象
     */
    private Wjjgxx buildWjjgxx(String ocrData) {
        Wjjgxx wjjgxx = new Wjjgxx();

        com.iflytek.icourt.model.pdf.OcrResult ocrResult = null;
        try {
            ocrResult = JSONObject.parseObject(ocrData, com.iflytek.icourt.model.pdf.OcrResult.class);
            if (ocrResult == null || ocrResult.getTaskResult() == null
                    || CollectionUtils.isEmpty(ocrResult.getTaskResult().getPage())) {
                logger.info("ocr识别结果为null, 构造文件结构信息对象结束");
                return wjjgxx;
            }
        } catch (Exception e) {
            logger.warn("构造文件结构对象失败, ocr 结果JSON转成Bean异常, 构造文件结构信息对象结束. ocrData: {}", ocrData, e);
            return wjjgxx;
        }

        List<Page> pageList = ocrResult.getTaskResult().getPage();
        List<Textline> textlineList = new ArrayList<>();
        // 获取识别结果中的文本区域
        for (Page page : pageList) {
            if (page == null) {
                continue;
            }

            // 先取文本区内的文本行, page -> textRegion -> texline
            if (CollectionUtils.isNotEmpty(page.getTextRegion())) {
                List<Textline> textlines = page.getTextRegion().parallelStream().filter(Objects::nonNull)
                        .map(TextRegion::getTextline).filter(CollectionUtils::isNotEmpty).flatMap(t -> t.stream())
                        .filter(Objects::nonNull).collect(Collectors.toList());
                CollectionUtils.addAll(textlineList, textlines);
            }

            // 再取表格区的文本行, page -> table -> cell -> textline
            if (CollectionUtils.isNotEmpty(page.getTable())) {
                List<Textline> textlines = page.getTable().stream().filter(Objects::nonNull).map(Table::getCell)
                        .parallel().filter(CollectionUtils::isNotEmpty).flatMap(c -> c.stream())
                        .filter(Objects::nonNull).map(Cell::getTextline).filter(CollectionUtils::isNotEmpty)
                        .flatMap(t -> t.stream()).filter(Objects::nonNull).collect(Collectors.toList());
                CollectionUtils.addAll(textlineList, textlines);
            }
        }
        if (CollectionUtils.isEmpty(textlineList)) {
            logger.info("ocr识别结果中没有文本行, 构造文件结构信息对象结束");
            return wjjgxx;
        }

        // 按 y 轴进行文本行排序
        textlineList.sort(Comparator.comparing(Textline::getRect,
                Comparator.comparing(Rect::getY, Comparator.comparingInt(y -> y))));

        // 生成文本信息
        List<Wbxx> wbxxList = buildWbxx(textlineList);
        wjjgxx.setWbxx(wbxxList);
        wjjgxx.setWbsl(CollectionUtils.size(wbxxList));

        // 提取出所有的手写体文本行
        List<Textline> handwriteTextLines = textlineList.stream()
                .filter(l -> OCR_HANDWRITE_TYPE.equalsIgnoreCase(l.getType()))
                .filter(l -> CollectionUtils.isNotEmpty(l.getSent()))
                .sorted(Comparator.comparing(Textline::getRect,
                        Comparator.comparing(Rect::getY, Comparator.comparingInt(y -> y))))
                .collect(Collectors.toList());
        // 生成手写信息
        List<Sxxx> sxxxList = buildSxxx(handwriteTextLines);
        wjjgxx.setSxxx(sxxxList);
        wjjgxx.setSxsl(CollectionUtils.size(sxxxList));

        logger.info("文件结构信息构造完成");
        return wjjgxx;
    }

    /**
     * 手写信息
     *
     * @param handwriteLineList
     */
    private List<Sxxx> buildSxxx(List<Textline> handwriteLineList) {
        List<Sxxx> sxxxList = new ArrayList<>();
        if (CollectionUtils.isEmpty(handwriteLineList)) {
            return sxxxList;
        }
        Collections.sort(handwriteLineList, (a, b) -> a.getRect().getY().compareTo(b.getRect().getY()));

        List<List<Textline>> resultGroup = byGroup(handwriteLineList, (t1, t2) -> {
            double mutipile = 1.5;
            int offset = Math.abs(t1.getRect().getY() - t2.getRect().getY());
            if (offset < (t1.getRect().h) / mutipile && offset < (t2.getRect().h / mutipile)) {
                return 0;
            } else {
                return 1;
            }
        });
        Collections.sort(resultGroup, (a, b) -> a.get(0).getRect().getY().compareTo(b.get(0).getRect().getY()));
        List<Sent> sentList = null;
        for (List<Textline> textlines1 : resultGroup) {
            Collections.sort(textlines1, (a, b) -> a.getRect().getX().compareTo(b.getRect().getX()));
            sentList = new ArrayList<>();
            if (textlines1.size() > 1) {
                logger.info("the same line");
            }
            for (int i = 0; i < textlines1.size(); i++) {
                Textline textline = textlines1.get(i);
                sentList.addAll(textline.getSent());
            }
            if (CollectionUtils.isEmpty(sentList)) {
                continue;
            }
            Sxxx sxxx = new Sxxx();
            // 提取文本
            String sentValue = sentList.stream().map(Sent::getValue).collect(Collectors.joining(""));
            sxxx.setWb(sentValue);

            // 构造文本坐标, 左上 右上 右下 左下
            ArrayList<Wbzb> wbzbList = buildWbzb(textlines1);
            sxxx.setWbzb(wbzbList);

            // 构造字符信息
            List<Zfxx> zfxxList = buildZfxx(sentList);
            sxxx.setZfxx(zfxxList);

            sxxxList.add(sxxx);

        }
        logger.debug("手写信息构造完成, sxxxSize: {}", CollectionUtils.size(sxxxList));
        return sxxxList;
    }

    /**
     * 文本信息
     *
     * @param textlineList
     */
    private List<Wbxx> buildWbxx(List<Textline> textlineList) {
        List<Wbxx> wbxxList = new ArrayList<>();

        if (CollectionUtils.isEmpty(textlineList)) {
            return wbxxList;
        }
        Collections.sort(textlineList, (a, b) -> a.getRect().getY().compareTo(b.getRect().getY()));
        List<List<Textline>> resultGroup = byGroup(textlineList, (t1, t2) -> {
            double mutipile = 1.5;
            int offset = Math.abs(t1.getRect().getY() - t2.getRect().getY());
            if (offset < (t1.getRect().h) / mutipile && offset < (t2.getRect().h / mutipile)) {
                return 0;
            } else {
                return 1;
            }
        });
        Collections.sort(resultGroup, (a, b) -> a.get(0).getRect().getY().compareTo(b.get(0).getRect().getY()));
        int Hxh = 0;
        // 构造文本信息
        List<Sent> sentList = null;
        for (List<Textline> textlines1 : resultGroup) {
            Collections.sort(textlines1, (a, b) -> a.getRect().getX().compareTo(b.getRect().getX()));
            sentList = new ArrayList<>();
            if (textlines1.size() > 1) {
                logger.info("the same line");
            }
            textlines1 = textlines1.stream().filter(v -> CollectionUtils.isNotEmpty(v.getSent())).collect(Collectors.toList());
            for (int i = 0; i < textlines1.size(); i++) {
                Textline textline = textlines1.get(i);
                sentList.addAll(textline.getSent());
            }
            if (CollectionUtils.isEmpty(sentList)) {
                continue;
            }
            Wbxx wbxx = new Wbxx();
            wbxx.setHxh(Hxh);
            wbxx.setDlxh(0);
            wbxx.setFlxh(0);

            // 提取行文本
            String sentValue = sentList.stream().map(Sent::getValue).collect(Collectors.joining(""));
            wbxx.setHwb(sentValue);

            // 行文本坐标, 左上 右上 右下 左下
            ArrayList<Wbzb> wbzbList = buildWbzb(textlines1);
            wbxx.setHwbzb(wbzbList);

            // 提取字符信息
            List<Zfxx> zfxxList = buildZfxx(sentList);
            wbxx.setZfxx(zfxxList);

            wbxxList.add(wbxx);
            Hxh++;
        }

        logger.debug("文本信息集合构造完成, wbxxSize: {}", CollectionUtils.size(wbxxList));
        return wbxxList;
    }

    /**
     * 构造文本坐标, 左上 右上 右下 左下
     */
    private ArrayList<Wbzb> buildWbzb(Textline textline) {
        Rect rect = textline.getRect();
        Integer x = rect.getX();
        Integer y = rect.getY();
        Integer w = rect.getW();
        Integer h = rect.getH();
        Wbzb tl = new Wbzb(x, y);
        Wbzb tr = new Wbzb(x + w, y);
        Wbzb br = new Wbzb(x + w, y + h);
        Wbzb bl = new Wbzb(x, y + h);
        return Lists.newArrayList(tl, tr, br, bl);
    }

    /**
     * 构造文本坐标, 左上 右上 右下 左下
     */
    private ArrayList<Wbzb> buildWbzb(List<Textline> textlines) {
		/*
		 * Rect rect = textline.getRect(); Integer x = rect.getX(); Integer y =
		 * rect.getY(); Integer w = rect.getW(); Integer h = rect.getH();
		 */
        Optional<Textline> option = textlines.stream().max(Comparator.comparingInt(v -> v.getRect().getH()));
        Textline maxTextline = option.get();

        Integer x = textlines.get(0).getRect().getX();
        Integer y = textlines.get(0).getRect().getY();
        Integer w = textlines.stream().mapToInt(v -> v.getRect().getW()).sum();
        Integer h = maxTextline.getRect().getH();

        Wbzb tl = new Wbzb(x, y);
        Wbzb tr = new Wbzb(x + w, y);
        Wbzb br = new Wbzb(x + w, y + h);
        Wbzb bl = new Wbzb(textlines.get(0).getRect().getX(),
                textlines.get(0).getRect().getY() + maxTextline.getRect().getH());
        return Lists.newArrayList(tl, tr, br, bl);
    }

    /**
     * 提取文本行中的所有字符, 并按坐标排序
     */
    private List<Zfxx> buildZfxx(List<Sent> sentList) {
        return sentList.stream().map(Sent::getChar).filter(CollectionUtils::isNotEmpty).flatMap(s -> s.stream())
                .sorted((a, b) -> {
                    // 先根据 x 轴, 再根据 y 轴对字符进行排序
                    Integer ax = a.getRect().getTl_point().getX();
                    Integer ay = a.getRect().getTl_point().getY();
                    Integer bx = b.getRect().getTl_point().getX();
                    Integer by = b.getRect().getTl_point().getY();
                    return ax - bx == 0 ? ay - by : ax - bx;
                }).map(c -> {
                    // 对象转换
                    Zfxx zfxx = new Zfxx();
                    zfxx.setZ(c.getValue());
                    Rect rect = c.getRect();
                    // 使用 右下 - 左上 坐标, 计算出字符高度和宽度
                    zfxx.setK(rect.getBr_point().getX() - rect.getTl_point().getX());
                    zfxx.setG(rect.getBr_point().getY() - rect.getTl_point().getY());
                    zfxx.setX(rect.getTl_point().getX());
                    zfxx.setY(rect.getTl_point().getY());
                    return zfxx;
                }).collect(Collectors.toList());
    }

    /**
     * 图片压缩并调用 ocr 引擎
     */
    private PageOcrInfo compressAndOcr(String wjbh, byte[] fileBytes, String wjhz,
                                       Map<String, String> ocrEngineParams) {
        PageOcrInfo info = new PageOcrInfo();
        info.setImgBytes(fileBytes);

        // 获取原图尺寸
        BufferedImage image = null;
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes)) {
            image = ImageIO.read(inputStream);
            info.setYskd(image.getWidth());
            info.setYsgd(image.getHeight());
            info.setKd(image.getWidth());
            info.setGd(image.getHeight());
        } catch (IOException e) {
            logger.warn("读取请求图片异常, 处理结束, wjbh: {}", wjbh, e);
            throw new ViewException(CommonResultEnum.EXCEPTION, "读取请求图片异常", e);
        }

        // 原图压缩
        int yskd = info.getYskd();
        int ysgd = info.getYsgd();
        logger.debug("原图尺寸, width: {}, height: {}, size: {}KB", yskd, ysgd, fileBytes.length / 1024);
        int maxLength = Math.max(ysgd, yskd);
        if (compressThreshold > 0 && maxLength > compressThreshold) {
            try (ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes);
                 ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream()) {

                double scale = Double.valueOf(compressThreshold) / maxLength;
                // 计算压缩后的图像尺寸
                int kd = (int) (yskd * scale);
                int gd = (int) (ysgd * scale);
                info.setKd(kd);
                info.setGd(gd);

                // BufferedImage result = new BufferedImage(kd, gd, BufferedImage.TYPE_INT_RGB);
                // Graphics graphics = result.getGraphics();
                // graphics.drawImage(image.getScaledInstance(kd, gd,
                // java.awt.Image.SCALE_SMOOTH), 0, 0, null);
                // graphics.dispose();
                // ImageIO.write(result, "JPEG", byteOutputStream);

                Thumbnails.of(inputStream).scale(scale).outputFormat(wjhz.substring(1))
                        .toOutputStream(byteOutputStream);
                info.setImgBytes(byteOutputStream.toByteArray());
                logger.debug("图片压缩完成, 压缩后尺寸, width: {}, height: {}, size: {}KB", info.getKd(), info.getGd(),
                        info.getImgBytes().length / 1024);
            } catch (IOException e) {
                logger.warn("图片压缩异常, wjbh: {}", wjbh, e);
            }
        }

        // 调用引擎
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(info.getImgBytes())) {
            logger.debug("开始请求ocr引擎");
            long start = System.currentTimeMillis();
            Result<String> ocrReqResult = ocrClient.request(inputStream, IdWokerUtil.nextId() + wjhz, ocrEngineParams);
            logger.debug("请求ocr引擎完成, 耗时: {}ms", System.currentTimeMillis() - start);

            if (ocrReqResult == null || !ocrReqResult.isSuccess()) {
                logger.warn("调用 OCR 引擎失败");
            } else {
                String ocrData = ocrReqResult.getData();
                info.setOcrData(ocrData);
            }
        } catch (Exception e) {
            logger.warn("调用OCR引擎异常, 处理结束, wjbh: {}", wjbh, e);
        }

        return info;
    }

    /**
     * 合成多页 pdf
     */
    public byte[] exportPdf(List<byte[]> imgByteList, List<String> ocrResultList) {
        if (CollectionUtils.isEmpty(imgByteList) || CollectionUtils.isEmpty(ocrResultList)) {
            logger.info("pdf合成失败, 图片字节或识别结果为空.");
            return null;
        }

        try {
            int size = Math.min(CollectionUtils.size(imgByteList), CollectionUtils.size(ocrResultList));
            if (size == 1) {
                byte[] pdfBytes = new com.iflytek.icourt.common.util.PdfComposeUtil().handleCompose(imgByteList.get(0),
                        ocrResultList.get(0));
                return pdfBytes;
            } else {
                final CountDownLatch countDownLatch = new CountDownLatch(size);
                byte[][] pdfByteArray = new byte[size][];

                for (int i = 0; i < size; i++) {
                    final int index = i;
                    PDF_CREATE_THREAD_POOL.execute(() -> {
                        try {
                            byte[] imageBytes = imgByteList.get(index);
                            String jsonResult = ocrResultList.get(index);
                            if (StringUtils.isBlank(jsonResult)) {
                                logger.info("识别结果为空, index: {}", index);
                            } else {
                                pdfByteArray[index] = new com.iflytek.icourt.common.util.PdfComposeUtil()
                                        .handleCompose(imageBytes, jsonResult);
                            }
                        } catch (Exception e) {
                            logger.warn("合成PDF子页面异常, index: {}", index, e);
                        } finally {
                            countDownLatch.countDown();
                        }
                    });
                }
                countDownLatch.await();

                List<byte[]> pdfByteList = Arrays.stream(pdfByteArray).filter(ArrayUtils::isNotEmpty)
                        .collect(Collectors.toList());
                if (CollectionUtils.isEmpty(pdfByteList)) {
                    logger.warn("pdf子页面独立合成结果为空");
                    return null;
                }

                return new com.iflytek.icourt.common.util.PdfComposeUtil().mergePdfList(pdfByteList);
            }
        } catch (Exception e) {
            logger.warn("合成PDF文件异常.", e);
            return null;
        }
    }

    /**
     * @param params
     * @return
     */
    @Override
    public WjOcrsbjgRes fileRecognize(OcrResultRequestVo params) throws Exception {
        WjOcrsbjgRes wjOcrsbjgRes = new WjOcrsbjgRes();
        wjOcrsbjgRes.getData().setWjbh(params.getWjbh());// 文件编号
        wjOcrsbjgRes.getData().setWjhz(params.getWjhz());// 文件后缀
        // 文件内容
        byte[] bytes = Base64.decodeBase64(params.getWjnr());
        String fileType = params.getWjhz().toLowerCase();
        switch (fileType) {
            case ".pdf":
                // 先做格式转换
                InputStream inputStream = new ByteArrayInputStream(bytes);
                List<OutputStream> list = new ArrayList<>();
                PDDocument doc = null;
                try {
                    doc = PDDocument.load(inputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                PDFRenderer renderer = new PDFRenderer(doc);
                int pageCount = doc.getNumberOfPages();
                for (int i = 0; i < pageCount; i++) {
                    int index = i + 1;
                    BufferedImage image = null;
                    try {
                        synchronized (renderer) {
                            image = renderer.renderImage(i, 1f);
                        }
                        OutputStream outputStream = new ByteArrayOutputStream();
                        ImageIO.write(image, "png", outputStream);
                        bytes = ((ByteArrayOutputStream) outputStream).toByteArray();
                    } catch (Exception ex) {
                        logger.info("pdf to image", ex);
                    }
                    break;
                }
            case ".png":
            case ".jpg":
                int width = 0;
                int height = 0;
                // 图片高度、宽度
                File file = ImageUtil.getFileFromBytes(bytes);
                try (InputStream is = new FileInputStream(file)) {
                    BufferedImage src = ImageIO.read(is);
                    width = src.getWidth(null); // 得到源图宽
                    height = src.getHeight(null);
                } catch (Exception e) {
                    logger.warn("getImgWidth {},{}", e, e.getMessage());
                }

                if (width > 2500 || height > 2500) {
                    double scale = 2500.0 / Math.max(width, height);
                    // 有一个边大于2500时，做一次图片压缩
                    OutputStream outputStream = new ByteArrayOutputStream();
                    bytes = ImageUtil.reduceImgByRate(bytes, params.getWjhz().substring(1), scale);
                    toFile(params.getWjhz(), bytes);
                }

                FileUtils.deleteQuietly(file);

                Result<String> result = ocrClient.request(params.getWjnr());
                String ocrResult = result.getData();
                wjOcrsbjgRes.getData().setOcrResultJson(ocrResult);
                com.iflytek.icourt.model.pdf.OcrResult ocrResultObj = JSONObject.parseObject(ocrResult,
                        com.iflytek.icourt.model.pdf.OcrResult.class);

                wjOcrsbjgRes.getData().setWjjgxx(getWbxx(ocrResultObj));
                wjOcrsbjgRes.getData().getWjsbxx().add(getWjsbxx(ocrResultObj));

                // 设置宽和高
                wjOcrsbjgRes.getData().getWjsbxx().get(0).setWjkd(width);
                wjOcrsbjgRes.getData().getWjsbxx().get(0).setWjgd(height);

                if (params.isSfwjnr()) {
                    byte[] pdfBytes = new PdfComposeUtil().handleCompose(bytes, ocrResult);
                    // toFile(".pdf",pdfBytes);
                    // 计算双层pdf的md5值，提取文本内容
                    String md5Str = DigestUtils.md5DigestAsHex(pdfBytes);
                    String pdfBase64Str = Base64.encodeBase64String(pdfBytes);
                    wjOcrsbjgRes.getData().setPdfnr(pdfBase64Str);
                    // 有两处pdf内容
                    wjOcrsbjgRes.getData().getWjsbxx().get(0).setWjnr(pdfBase64Str);
                    wjOcrsbjgRes.getData().setMd5(md5Str);
                }
                if (params.isSfwjwb()) {
                    List<com.iflytek.icourt.model.pdf.Page> pageList = ocrResultObj.getTaskResult().getPage();
                    tableCorrectCheck(pageList);
                    // word字号
                    List<String> exportParam = new ArrayList<>();
                    exportParam.add("15");
                    // exportParam.add(exportProperties.getWordFamilyName());
                    List<com.iflytek.icourt.model.pdf.Page> pageListParam = new ArrayList<>();
                    pageListParam.addAll(pageList);
                    try {
                        byte[] fileBytes = txtFileExportStrategy.createFileByOcrResultPageList(pageListParam, "txt",
                                exportParam);
                        String txtNr = new String(fileBytes, "gbk");
                        // 设置两次文本内容
                        wjOcrsbjgRes.getData().setTxtnr(txtNr);
                        wjOcrsbjgRes.getData().getWjsbxx().get(0).setWjwb(txtNr);
                    } catch (Exception ex) {
                        logger.error("error", ex);
                        ex.toString();
                    }
                }
                break;

        }

        return wjOcrsbjgRes;
    }

    /**
     * @param bmsah
     * @param bsbhlist
     * @param dwbm
     * @param jzbh
     */
    @Override
    public void unRelationShipTaskAndBMSAH(String bmsah, String[] bsbhlist, String dwbm, String jzbh) {
        // 先更新任务
        QueryWrapper<OcrTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().in(OcrTask::getBsbh, bsbhlist);
        List<OcrTask> taskList = ocrTaskService.list(queryWrapper);

        if (taskList.size() > 0) {

            taskList.forEach(c -> {
                c.setBmsah("");
                c.setDwbm("");
                c.setJzbh("");// add jzbh
            });

            ocrTaskService.updateBatchById(taskList);
        } else {
            // return;
        }

        // 再更新任务下的文件集合
        QueryWrapper<OcrFile> queryWrapperFile = new QueryWrapper<>();
        queryWrapperFile.lambda().in(OcrFile::getBsbh, bsbhlist);
        List<OcrFile> files = ocrFileService.list(queryWrapperFile);
        files.forEach(c -> {
            c.setBmsah("");
        });

        if (files.size() > 0) {
            ocrFileService.updateBatchById(files);
        }

        // 图像识别任务也要关联部门受案号
        QueryWrapper<RecognizeTask> queryWrapperRecognizeTask = new QueryWrapper<>();
        queryWrapperRecognizeTask.lambda().in(RecognizeTask::getBsbh, bsbhlist);
        List<RecognizeTask> recognizeTaskList = recognizeTaskService.list(queryWrapperRecognizeTask);

        if (recognizeTaskList.size() > 0) {
            recognizeTaskList.forEach(c -> {
                c.setBmsah("");
                c.setDwbm("");
                c.setJzbh("");// add jzbh
            });
            recognizeTaskService.updateBatchById(recognizeTaskList);
        }

        // 图像识别的文件也一起更新一下
        QueryWrapper<RecognizeFile> recognizeFileQueryWrapper = new QueryWrapper<>();
        recognizeFileQueryWrapper.lambda().in(RecognizeFile::getBsbh, bsbhlist);
        List<RecognizeFile> recognizeFiles = recognizeFileService.list(recognizeFileQueryWrapper);

        if (recognizeFiles.size() > 0) {
            recognizeFiles.forEach(c -> {
                c.setBmsah("");
            });
            recognizeFileService.updateBatchById(recognizeFiles);
        }
    }

    /**
     * 处理异常情况，null等
     *
     * @param pageList
     */
    private void tableCorrectCheck(List<com.iflytek.icourt.model.pdf.Page> pageList) {
        for (Iterator var6 = pageList.iterator(); var6.hasNext(); ) {
            Page page = (Page) var6.next();
            if (page.getTable() == null) {
                page.setTable(new ArrayList<Table>());
            }

            Iterator var8 = page.getTable().iterator();

            while (var8.hasNext()) {
                Table table = (Table) var8.next();
                Iterator var9 = table.getCell().iterator();
                while (var9.hasNext()) {
                    com.iflytek.icourt.model.pdf.Cell cell = (com.iflytek.icourt.model.pdf.Cell) var9.next();
                    cell.toString();
                    if (cell.getTextline() == null) {
                        cell.setTextline(new ArrayList<>());
                    }
                }
            }
        }
    }

    /**
     * @param ocrResult
     * @return
     */
    private WjOcrsbjgResDataWjjgxx getWbxx(com.iflytek.icourt.model.pdf.OcrResult ocrResult) {
        // 结果文本信息，获取文本信息
        WjOcrsbjgResDataWjjgxx wjOcrsbjgResDataWjjgxx = new WjOcrsbjgResDataWjjgxx();
        List<WjOcrsbjgResDataWjjgxxWbxx> wjOcrsbjgResDataWjjgxxWbxxes = new ArrayList<>();

        List<Textline> sortTextlines = sortTextline(ocrResult);

        Iterator iterator = sortTextlines.iterator();

        int hanghao = 1;
        while (iterator.hasNext()) {
            Textline textline = (Textline) iterator.next();

            WjOcrsbjgResDataWjjgxxWbxx wjOcrsbjgResDataWjjgxxWbxx = new WjOcrsbjgResDataWjjgxxWbxx();
            // 设置行文本
            wjOcrsbjgResDataWjjgxxWbxx.setHwb(textline.getSent().get(0).getValue());
            wjOcrsbjgResDataWjjgxxWbxx.setHxh(hanghao);
            wjOcrsbjgResDataWjjgxxWbxx.setDlxh(1);// 段落序号
            hanghao++;
            wjOcrsbjgResDataWjjgxx.getWbxx().add(wjOcrsbjgResDataWjjgxxWbxx);

            int x1 = textline.getRect().getX();
            int x2 = textline.getRect().getX() + textline.getRect().getW();
            int y1 = textline.getRect().getY();
            int y2 = textline.getRect().getY() + textline.getRect().getH();

            WjOcrsbjgResDataWjjgxxWbxxHwbzb p1zb = new WjOcrsbjgResDataWjjgxxWbxxHwbzb();
            p1zb.setX(x1);
            p1zb.setY(y1);
            wjOcrsbjgResDataWjjgxxWbxx.getHwbzb().add(p1zb);
            WjOcrsbjgResDataWjjgxxWbxxHwbzb p2zb = new WjOcrsbjgResDataWjjgxxWbxxHwbzb();
            p2zb.setX(x2);
            p2zb.setY(y1);
            wjOcrsbjgResDataWjjgxxWbxx.getHwbzb().add(p2zb);
            WjOcrsbjgResDataWjjgxxWbxxHwbzb p3zb = new WjOcrsbjgResDataWjjgxxWbxxHwbzb();
            p2zb.setX(x1);
            p2zb.setY(y2);
            wjOcrsbjgResDataWjjgxxWbxx.getHwbzb().add(p3zb);
            WjOcrsbjgResDataWjjgxxWbxxHwbzb p4zb = new WjOcrsbjgResDataWjjgxxWbxxHwbzb();
            p2zb.setX(x2);
            p2zb.setY(y2);
            wjOcrsbjgResDataWjjgxxWbxx.getHwbzb().add(p4zb);
        }

        return wjOcrsbjgResDataWjjgxx;
    }

    /**
     * @param ocrResult
     * @return
     */
    private WjOcrsbjgResDataWjsbxx getWjsbxx(com.iflytek.icourt.model.pdf.OcrResult ocrResult) {
        // 识别信息，包含（文本、表格、手写体）
        WjOcrsbjgResDataWjsbxx wjOcrsbjgResDataWjsbxx = new WjOcrsbjgResDataWjsbxx();
        wjOcrsbjgResDataWjsbxx.setWjqxjd(ocrResult.getTaskResult().getPage().get(0).getOrientation());
        wjOcrsbjgResDataWjsbxx.setWjpzj(ocrResult.getTaskResult().getPage().get(0).getOrientation());

        // 文本区域赋值
        List<Textline> sortTextlines = sortTextline(ocrResult);
        Iterator iteratorSent = sortTextlines.iterator();

        // 印刷体
        WjOcrsbjgResDataWjsbxxWzqy wjOcrsbjgResDataWjsbxxWzqy = new WjOcrsbjgResDataWjsbxxWzqy();
        wjOcrsbjgResDataWjsbxx.getWzqy().add(wjOcrsbjgResDataWjsbxxWzqy);

        // 手写体
        WjOcrsbjgResDataWjsbxxSxqy wjOcrsbjgResDataWjsbxxSxqy = new WjOcrsbjgResDataWjsbxxSxqy();
        wjOcrsbjgResDataWjsbxx.getSxqy().add(wjOcrsbjgResDataWjsbxxSxqy);

        // 表格区域

        int hanghao = 1;
        while (iteratorSent.hasNext()) {
            Textline textline = (Textline) iteratorSent.next();

            if (textline.getType() == "HANDWRITE") {
                wjOcrsbjgResDataWjsbxxSxqy.setZbwz(getSentPosition(textline));
                wjOcrsbjgResDataWjsbxxSxqy.setZf(getZfs(textline));
            } else {
                WjOcrsbjgResDataWjsbxxWzqyH wjOcrsbjgResDataWjsbxxWzqyH = new WjOcrsbjgResDataWjsbxxWzqyH();
                wjOcrsbjgResDataWjsbxxWzqyH.setXh(hanghao);
                hanghao++;
                Iterator iteratorChar = textline.getSent().get(0).getChar().iterator();
                while (iteratorChar.hasNext()) {
                    Char aChar = (Char) (iteratorChar.next());
                    Rect rect = aChar.getRect();//
                    String charPosition = rect.getTl_point().getX() + "," + rect.getTl_point().getY() + ","
                            + rect.getBr_point().getX() + "," + rect.getBr_point().getY();
                    wjOcrsbjgResDataWjsbxxWzqyH.getZfzbwz().add(charPosition);
                    wjOcrsbjgResDataWjsbxxWzqyH.getZf().add(aChar.getValue());
                }
                // 矩形的坐标位置
                String sentPosition = getSentPosition(textline);
                wjOcrsbjgResDataWjsbxxWzqyH.setZbwz(sentPosition);
                wjOcrsbjgResDataWjsbxxWzqy.getH().add(wjOcrsbjgResDataWjsbxxWzqyH);
            }
        }

        // 表格区域中的文字部分填充
        WjOcrsbjgResDataWjsbxxBgqyText wjOcrsbjgResDataWjsbxxBgqyText = new WjOcrsbjgResDataWjsbxxBgqyText();
        wjOcrsbjgResDataWjsbxxBgqyText.setLx("text");
        wjOcrsbjgResDataWjsbxxBgqyText.setH(wjOcrsbjgResDataWjsbxx.getWzqy().get(0).getH());
        wjOcrsbjgResDataWjsbxx.getBgqy().add(wjOcrsbjgResDataWjsbxxBgqyText);
        // 表格部分填充
        WjOcrsbjgResDataWjsbxxBgqyTable wjOcrsbjgResDataWjsbxxBgqyTable = new WjOcrsbjgResDataWjsbxxBgqyTable();
        wjOcrsbjgResDataWjsbxxBgqyTable.setLx("table");
        wjOcrsbjgResDataWjsbxx.getBgqy().add(wjOcrsbjgResDataWjsbxxBgqyTable);

        if (ocrResult.getTaskResult().getPage().get(0).getTable().size() > 0) {
            // 存在表格，将表格中的内容格式化一下,需要分组并排序
            Table table = ocrResult.getTaskResult().getPage().get(0).getTable().get(0);

            // 表格的坐标位置
            wjOcrsbjgResDataWjsbxxBgqyTable.setZbwz(getTablePosition(table));

            List<Cell> cells = table.getCell();
            List<List<Cell>> resultGroup = byGroup(cells, (t1, t2) -> {
                double mutipile = 1.5;
                int offset = Math.abs(t1.getRect().getY() - t2.getRect().getY());
                if (offset < (t1.getRect().h) / mutipile && offset < (t2.getRect().h / mutipile)) {
                    return 0;
                } else {
                    return 1;
                }
            });

            Iterator trIterator = resultGroup.iterator();
            while (trIterator.hasNext()) {
                WjOcrsbjgResDataWjsbxxBgqyTableTrs wjOcrsbjgResDataWjsbxxBgqyTableTrs = new WjOcrsbjgResDataWjsbxxBgqyTableTrs();

                // 一行的坐标位置，没写
                wjOcrsbjgResDataWjsbxxBgqyTableTrs.setZbwz("");
                wjOcrsbjgResDataWjsbxxBgqyTable.getTrs().add(wjOcrsbjgResDataWjsbxxBgqyTableTrs);

                // 一行的数据
                List<Cell> cells1 = (List<Cell>) trIterator.next();
                Iterator tdIterator = cells1.iterator();
                while (tdIterator.hasNext()) {
                    Cell cell1 = (Cell) tdIterator.next();
                    WjOcrsbjgResDataWjsbxxBgqyTableTrsTds wjOcrsbjgResDataWjsbxxBgqyTableTrsTds = new WjOcrsbjgResDataWjsbxxBgqyTableTrsTds();

                    List<Textline> tdTextlines = cell1.getTextline();

                    if (tdTextlines != null) {
                        wjOcrsbjgResDataWjsbxxBgqyTableTrsTds.setZbwz(getCellPosition(cell1));
                        wjOcrsbjgResDataWjsbxxBgqyTableTrsTds.setWb(getTextFromTextlines(tdTextlines));

                        wjOcrsbjgResDataWjsbxxBgqyTableTrs.getTds().add(wjOcrsbjgResDataWjsbxxBgqyTableTrsTds);
                    } else {
                        String debug = "";
                        debug.toString();
                    }
                }
            }
        }

        return wjOcrsbjgResDataWjsbxx;
    }

    /**
     * 根据传入的比较器进行集合的分组
     *
     * @param data 需要分组的元素集合
     * @param c    传入的比较器
     * @param <T>  元素
     * @return 返回分组后的集合列表
     */
    private static <T> List<List<T>> byGroup(Collection<T> data, Comparator<? super T> c) {
        // 方法上使用泛型 记得在返回值前加<T>
        List<List<T>> result = new ArrayList<>();
        for (T t : data) {// 1.循环取出集合中的每个元素
            boolean isSameGroup = false;// 2.标志为不是同组
            for (List<T> aResult : result) {// 4.循环查找当前元素是否属于某个已创建的组
                if (c.compare(t, aResult.get(0)) == 0) {// aResult.get(0)表示：只要当前元素和某个组的第一个元素通过比较器比较相等则属于该组
                    isSameGroup = true;// 5.查询到当前元素属于某个组则设置标志位，不让其创键新组
                    aResult.add(t);// 6.把当前元素添加到当前组
                    break;
                }
            }
            if (!isSameGroup) {// 3.不属于任何组的则创建一个新组，并把元素添加到该组
                // 创建
                List<T> innerList = new ArrayList<>();
                innerList.add(t);
                result.add(innerList);
            }
        }
        return result;
    }

    /**
     * 写成文件
     *
     * @param fileExt
     * @param bytes
     */
    private void toFile(String fileExt, byte[] bytes) {
        try {
            String tempFileName = UUID.randomUUID().toString();
            File file = File.createTempFile(tempFileName, fileExt);
            FileOutputStream fileOutputStream = null;
            fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 排序
     *
     * @param ocrResult
     * @return
     */
    private List<Textline> sortTextline(com.iflytek.icourt.model.pdf.OcrResult ocrResult) {
        // 迭代行文本
        Page page = ocrResult.getTaskResult().getPage().get(0);
        List<Textline> sortTextlines = new ArrayList<>();
        List<Textline> textlines = page.getTextRegion().get(0).getTextline();

        Collections.sort(textlines, (a, b) -> a.getRect().getY().compareTo(b.getRect().getY()));
        List<List<Textline>> resultGroup = byGroup(textlines, (t1, t2) -> {
            double mutipile = 1.5;
            int offset = Math.abs(t1.getRect().getY() - t2.getRect().getY());
            if (offset < (t1.getRect().h) / mutipile && offset < (t2.getRect().h / mutipile)) {
                return 0;
            } else {
                return 1;
            }
        });
        for (List<Textline> textlines1 : resultGroup) {
            Collections.sort(textlines1, (a, b) -> a.getRect().getX().compareTo(b.getRect().getX()));
            if (textlines1.size() > 1) {
                logger.info("the same line");
            }
            for (Textline textline : textlines1) {
                sortTextlines.add(textline);
            }
        }
        return sortTextlines;
    }

    /**
     * @param textline
     * @return
     */
    private String getSentPosition(Textline textline) {
        int x1 = textline.getRect().getX();
        int x2 = textline.getRect().getX() + textline.getRect().getW();
        int y1 = textline.getRect().getY();
        int y2 = textline.getRect().getY() + textline.getRect().getH();

        return x1 + "," + y1 + "," + x2 + "," + y2;
    }

    /**
     * @param textline
     * @return
     */
    private List<String> getZfs(Textline textline) {
        List<String> zfs = new ArrayList<>();
        Iterator iteratorChar = textline.getSent().get(0).getChar().iterator();
        while (iteratorChar.hasNext()) {
            Char aChar = (Char) (iteratorChar.next());
            Rect rect = aChar.getRect();//

            zfs.add(aChar.getValue());
        }
        return zfs;
    }

    /**
     * @param table
     * @return
     */
    private String getTablePosition(Table table) {
        Rect rect = table.getRect();
        return rect.getX() + "," + rect.getY() + "," + (rect.getX() + rect.getW()) + "," + (rect.getY() + rect.getH());
    }

    /**
     * @param tdTextlines
     * @return
     */
    private String getTextFromTextlines(List<Textline> tdTextlines) {
        tdTextlines.toString();
        tdTextlines.sort((a, b) -> a.getRect().getY().compareTo(b.getRect().getY()));
        Iterator iterator = tdTextlines.iterator();
        StringBuffer sb = new StringBuffer();
        while (iterator.hasNext()) {
            Textline textline = (Textline) iterator.next();
            List<Sent> sents = textline.getSent();
            if (CollectionUtils.isEmpty(sents)) {
                continue;
            }
            Sent sent = sents.get(0);
            if (sent == null) {
                continue;
            }
            String value = sent.getValue();
            if (StringUtils.isEmpty(value)) {
                continue;
            }
            sb.append(value);
        }
        return sb.toString();
    }

    /**
     * @param cell1
     * @return
     */
    private String getCellPosition(Cell cell1) {
        Rect rect = cell1.getRect();
        return rect.getX() + "," + rect.getY() + "," + (rect.getX() + rect.getW()) + "," + (rect.getY() + rect.getH());
    }

    @Override
    public WebResult<JzRecognizeResult> recognize(String bmsah) {
        List<OcrFile> ocrFileList = ocrFileService.findBybmsah(bmsah);
        if (CollectionUtils.isEmpty(ocrFileList)) {
            logger.warn("根据部门受案号{}查询OcrFile查询结果为空", bmsah);
            return WebResult.success(null);
        }
        JzRecognizeResult JzRecognizeResult = new JzRecognizeResult();
        List<com.iflytek.jzcpx.procuracy.ocr.entity.txsb.Jzmlwj> jzmlwjList = new ArrayList<com.iflytek.jzcpx.procuracy.ocr.entity.txsb.Jzmlwj>(
                ocrFileList.size());
        com.iflytek.jzcpx.procuracy.ocr.entity.txsb.Jzmlwj jzmlwj = null;

        List<RecognizeFuncEnum> funcList = new ArrayList<>();
        // 是否识别标题
        funcList.add(RecognizeFuncEnum.TITLE);
        // 是否识别手写签名
        funcList.add(RecognizeFuncEnum.SIGNATURE);
        // 是否识别印章
        funcList.add(RecognizeFuncEnum.Seal);
        // 是否识别指纹
        funcList.add(RecognizeFuncEnum.Fingerprint);

        for (int i = 0; i < ocrFileList.size(); i++) {
            OcrFile ocrFile = ocrFileList.get(i);
            String wjxh = ocrFile.getWjxh();
            String ocrJSON = ocrAnew(wjxh);
            //被定期删除掉的文件，需要重新调用ocr识别

            if (!StringUtils.isEmpty(ocrJSON)) {
                jzmlwj = new com.iflytek.jzcpx.procuracy.ocr.entity.txsb.Jzmlwj();
                jzmlwj.setWjxh(ocrFile.getWjxh());
                jzmlwj.setSfkby(StringUtils.isEmpty(ocrJSON) ? "true" : "false");

                // 文件特征结果
                List<Wjtz> wjtzList = new ArrayList<>();
                for (RecognizeFuncEnum funcEnum : funcList) {
                    wjtzList.addAll(funcHandler.handle(funcEnum, ocrJSON));
                }
                jzmlwj.setWjtz(wjtzList);
                jzmlwjList.add(jzmlwj);
                uploadToFdfs(ocrFile.getId(), ocrJSON);
            }
        }
        JzRecognizeResult.setBmsah(bmsah);
        JzRecognizeResult.setJzmlwj(jzmlwjList);
        return WebResult.success(JzRecognizeResult);
    }
}
